Chromium Code Reviews| Index: chrome/browser/ui/passwords/manage_passwords_bubble_model.cc |
| diff --git a/chrome/browser/ui/passwords/manage_passwords_bubble_model.cc b/chrome/browser/ui/passwords/manage_passwords_bubble_model.cc |
| index 3ea39f6ee3c32b076794c0654ca40bbff2117689..2742e7482bb13b55c49511fda6d2d6ce157d9c74 100644 |
| --- a/chrome/browser/ui/passwords/manage_passwords_bubble_model.cc |
| +++ b/chrome/browser/ui/passwords/manage_passwords_bubble_model.cc |
| @@ -71,15 +71,118 @@ password_bubble_experiment::SmartLockBranding GetSmartLockBrandingState( |
| } // 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; |
|
vabr (Chromium)
2016/05/31 18:38:22
nit: Please separate from the constructor with a b
vasilii
2016/06/01 10:21:53
Done.
|
| + |
| + // Records UMA events and updates the interaction statistics when the bubble |
| + // is closed. |
| + void ReportInteractions(const ManagePasswordsBubbleModel* model); |
| + |
| + void set_dismissal_reason( |
| + password_manager::metrics_util::UIDismissalReason reason) { |
| + dismissal_reason_ = reason; |
| + } |
| + |
| + void set_update_password_submission_event( |
| + password_manager::metrics_util::UpdatePasswordSubmissionEvent event) { |
| + update_password_submission_event_ = event; |
| + } |
| + |
| + void SetClockForTesting(std::unique_ptr<base::Clock> clock) { |
| + clock_ = std::move(clock); |
| + } |
| + |
| + private: |
| + // The way the bubble appeared. |
| + const password_manager::metrics_util::UIDisplayDisposition |
| + display_disposition_; |
| + |
| + // Dismissal reason for a bubble. |
| + password_manager::metrics_util::UIDismissalReason dismissal_reason_; |
| + |
| + // Dismissal reason for the update bubble. |
| + password_manager::metrics_util::UpdatePasswordSubmissionEvent |
| + update_password_submission_event_; |
| + |
| + // Current statistics for the save password bubble; |
| + password_manager::InteractionsStats interaction_stats_; |
| + |
| + // Used to retrieve the current time, in base::Time units. |
| + std::unique_ptr<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), |
| + update_password_submission_event_(metrics_util::NO_UPDATE_SUBMISSION), |
| + interaction_stats_(std::move(stats)), |
| + clock_(new base::DefaultClock) { |
| +} |
| + |
| +void ManagePasswordsBubbleModel::InteractionKeeper::ReportInteractions( |
| + const ManagePasswordsBubbleModel* model) { |
| + if (model->state() == password_manager::ui::PENDING_PASSWORD_STATE) { |
| + Profile* profile = model->GetProfile(); |
| + if (profile) { |
| + if (GetSmartLockBrandingState(profile) == |
| + password_bubble_experiment::SmartLockBranding::FULL) { |
| + password_bubble_experiment::RecordSavePromptFirstRunExperienceWasShown( |
| + profile->GetPrefs()); |
| + } |
| + 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::EXPLICIT_ACCESS).get(); |
|
vabr (Chromium)
2016/05/31 18:38:22
Should this be IMPLICIT_ACCESS? In other words, is
vasilii
2016/06/01 10:21:53
Done.
|
| + password_store->AddSiteStats(interaction_stats_); |
| + } |
| + } |
| + } |
| + |
| + if (model->state() != password_manager::ui::PENDING_PASSWORD_UPDATE_STATE) { |
| + // We have separate metrics for the Update bubble so do not record dismissal |
| + // reason for it. |
| + metrics_util::LogUIDismissalReason(dismissal_reason_); |
| + } |
| + |
| + PasswordsModelDelegate* delegate = model->web_contents() |
| + ? PasswordsModelDelegateFromWebContents(model->web_contents()) |
| + : nullptr; |
| + // Check if this was update password and record update statistics. |
| + if (update_password_submission_event_ == metrics_util::NO_UPDATE_SUBMISSION && |
| + (model->state() == password_manager::ui::PENDING_PASSWORD_UPDATE_STATE || |
| + model->state() == password_manager::ui::PENDING_PASSWORD_STATE)) { |
| + update_password_submission_event_ = |
| + model->GetUpdateDismissalReason(NO_INTERACTION); |
| + if (model->state() == password_manager::ui::PENDING_PASSWORD_UPDATE_STATE && |
| + delegate) |
| + delegate->OnNoInteractionOnUpdate(); |
| + } |
| + if (update_password_submission_event_ != metrics_util::NO_UPDATE_SUBMISSION) |
| + LogUpdatePasswordSubmissionEvent(update_password_submission_event_); |
| +} |
| + |
| ManagePasswordsBubbleModel::ManagePasswordsBubbleModel( |
| content::WebContents* web_contents, |
| DisplayReason display_reason) |
| : content::WebContentsObserver(web_contents), |
| - password_overridden_(false), |
| - display_disposition_(metrics_util::AUTOMATIC_WITH_PASSWORD_PENDING), |
| - dismissal_reason_(metrics_util::NO_DIRECT_INTERACTION), |
| - update_password_submission_event_(metrics_util::NO_UPDATE_SUBMISSION), |
| - clock_(new base::DefaultClock) { |
| + password_overridden_(false) { |
| PasswordsModelDelegate* delegate = |
| PasswordsModelDelegateFromWebContents(web_contents); |
| @@ -109,6 +212,7 @@ ManagePasswordsBubbleModel::ManagePasswordsBubbleModel( |
| UpdateManageStateTitle(); |
| } |
| + password_manager::InteractionsStats interaction_stats; |
| if (state_ == password_manager::ui::CONFIRMATION_STATE) { |
| base::string16 save_confirmation_link = |
| l10n_util::GetStringUTF16(IDS_MANAGE_PASSWORDS_LINK); |
| @@ -129,14 +233,14 @@ ManagePasswordsBubbleModel::ManagePasswordsBubbleModel( |
| save_confirmation_link_range_ = |
| gfx::Range(offset, offset + save_confirmation_link.length()); |
| } else if (state_ == password_manager::ui::PENDING_PASSWORD_STATE) { |
| - interaction_stats_.origin_domain = origin_.GetOrigin(); |
| - interaction_stats_.username_value = pending_password_.username_value; |
| + interaction_stats.origin_domain = origin_.GetOrigin(); |
| + interaction_stats.username_value = pending_password_.username_value; |
| 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; |
| + 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; |
| } |
| } else if (state_ == password_manager::ui::PENDING_PASSWORD_UPDATE_STATE) { |
| password_overridden_ = delegate->IsPasswordOverridden(); |
| @@ -145,17 +249,19 @@ ManagePasswordsBubbleModel::ManagePasswordsBubbleModel( |
| manage_link_ = |
| l10n_util::GetStringUTF16(IDS_OPTIONS_PASSWORDS_MANAGE_PASSWORDS_LINK); |
| + 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; |
| + display_disposition = metrics_util::MANUAL_WITH_PASSWORD_PENDING; |
| break; |
| case password_manager::ui::PENDING_PASSWORD_UPDATE_STATE: |
| - display_disposition_ = |
| + display_disposition = |
| metrics_util::MANUAL_WITH_PASSWORD_PENDING_UPDATE; |
| break; |
| case password_manager::ui::MANAGE_STATE: |
| - display_disposition_ = metrics_util::MANUAL_MANAGE_PASSWORDS; |
| + display_disposition = metrics_util::MANUAL_MANAGE_PASSWORDS; |
| break; |
| case password_manager::ui::CONFIRMATION_STATE: |
| case password_manager::ui::CREDENTIAL_REQUEST_STATE: |
| @@ -167,18 +273,18 @@ ManagePasswordsBubbleModel::ManagePasswordsBubbleModel( |
| } else { |
| switch (state_) { |
| case password_manager::ui::PENDING_PASSWORD_STATE: |
| - display_disposition_ = metrics_util::AUTOMATIC_WITH_PASSWORD_PENDING; |
| + display_disposition = metrics_util::AUTOMATIC_WITH_PASSWORD_PENDING; |
| break; |
| case password_manager::ui::PENDING_PASSWORD_UPDATE_STATE: |
| - display_disposition_ = |
| + display_disposition = |
| metrics_util::AUTOMATIC_WITH_PASSWORD_PENDING_UPDATE; |
| break; |
| case password_manager::ui::CONFIRMATION_STATE: |
| - display_disposition_ = |
| + display_disposition = |
| metrics_util::AUTOMATIC_GENERATED_PASSWORD_CONFIRMATION; |
| break; |
| case password_manager::ui::AUTO_SIGNIN_STATE: |
| - display_disposition_ = metrics_util::AUTOMATIC_SIGNIN_TOAST; |
| + display_disposition = metrics_util::AUTOMATIC_SIGNIN_TOAST; |
| break; |
| case password_manager::ui::MANAGE_STATE: |
| case password_manager::ui::CREDENTIAL_REQUEST_STATE: |
| @@ -187,103 +293,67 @@ ManagePasswordsBubbleModel::ManagePasswordsBubbleModel( |
| break; |
| } |
| } |
| - metrics_util::LogUIDisplayDisposition(display_disposition_); |
| + metrics_util::LogUIDisplayDisposition(display_disposition); |
| + interaction_keeper_.reset(new InteractionKeeper(std::move(interaction_stats), |
| + display_disposition)); |
| delegate->OnBubbleShown(); |
| } |
| ManagePasswordsBubbleModel::~ManagePasswordsBubbleModel() { |
| - if (state_ == password_manager::ui::PENDING_PASSWORD_STATE) { |
| - Profile* profile = GetProfile(); |
| - if (profile) { |
| - if (GetSmartLockBrandingState(profile) == |
| - password_bubble_experiment::SmartLockBranding::FULL) { |
| - password_bubble_experiment::RecordSavePromptFirstRunExperienceWasShown( |
| - profile->GetPrefs()); |
| - } |
| - 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::EXPLICIT_ACCESS) |
| - .get(); |
| - password_store->AddSiteStats(interaction_stats_); |
| - } |
| - } |
| - } |
| + interaction_keeper_->ReportInteractions(this); |
| + // web_contents() is nullptr if the tab is closing. |
| PasswordsModelDelegate* delegate = |
| web_contents() ? PasswordsModelDelegateFromWebContents(web_contents()) |
| : nullptr; |
| if (delegate) |
| delegate->OnBubbleHidden(); |
| - if (dismissal_reason_ == metrics_util::NOT_DISPLAYED) |
| - return; |
| - |
| - if (state_ != password_manager::ui::PENDING_PASSWORD_UPDATE_STATE) { |
| - // We have separate metrics for the Update bubble so do not record dismissal |
| - // reason for it. |
| - metrics_util::LogUIDismissalReason(dismissal_reason_); |
| - } |
| - // Check if this was update password and record update statistics. |
| - if (update_password_submission_event_ == metrics_util::NO_UPDATE_SUBMISSION && |
| - (state_ == password_manager::ui::PENDING_PASSWORD_UPDATE_STATE || |
| - state_ == password_manager::ui::PENDING_PASSWORD_STATE)) { |
| - update_password_submission_event_ = |
| - GetUpdateDismissalReason(NO_INTERACTION); |
| - if (state_ == password_manager::ui::PENDING_PASSWORD_UPDATE_STATE && |
| - delegate) |
| - delegate->OnNoInteractionOnUpdate(); |
| - } |
| - if (update_password_submission_event_ != metrics_util::NO_UPDATE_SUBMISSION) |
| - LogUpdatePasswordSubmissionEvent(update_password_submission_event_); |
| } |
| void ManagePasswordsBubbleModel::OnNeverForThisSiteClicked() { |
| DCHECK_EQ(password_manager::ui::PENDING_PASSWORD_STATE, state_); |
| - dismissal_reason_ = metrics_util::CLICKED_NEVER; |
| - update_password_submission_event_ = GetUpdateDismissalReason(NOPE_CLICKED); |
| + interaction_keeper_->set_dismissal_reason(metrics_util::CLICKED_NEVER); |
| + interaction_keeper_->set_update_password_submission_event( |
| + GetUpdateDismissalReason(NOPE_CLICKED)); |
| CleanStatisticsForSite(web_contents(), origin_); |
| PasswordsModelDelegateFromWebContents(web_contents())->NeverSavePassword(); |
| } |
| void ManagePasswordsBubbleModel::OnSaveClicked() { |
| DCHECK_EQ(password_manager::ui::PENDING_PASSWORD_STATE, state_); |
| - dismissal_reason_ = metrics_util::CLICKED_SAVE; |
| - update_password_submission_event_ = GetUpdateDismissalReason(UPDATE_CLICKED); |
| + interaction_keeper_->set_dismissal_reason(metrics_util::CLICKED_SAVE); |
| + interaction_keeper_->set_update_password_submission_event( |
| + GetUpdateDismissalReason(UPDATE_CLICKED)); |
| CleanStatisticsForSite(web_contents(), origin_); |
| PasswordsModelDelegateFromWebContents(web_contents())->SavePassword(); |
| } |
| void ManagePasswordsBubbleModel::OnNopeUpdateClicked() { |
| - update_password_submission_event_ = GetUpdateDismissalReason(NOPE_CLICKED); |
| + interaction_keeper_->set_update_password_submission_event( |
| + GetUpdateDismissalReason(NOPE_CLICKED)); |
| PasswordsModelDelegateFromWebContents(web_contents())->OnNopeUpdateClicked(); |
| } |
| void ManagePasswordsBubbleModel::OnUpdateClicked( |
| const autofill::PasswordForm& password_form) { |
| - update_password_submission_event_ = GetUpdateDismissalReason(UPDATE_CLICKED); |
| + interaction_keeper_->set_update_password_submission_event( |
| + GetUpdateDismissalReason(UPDATE_CLICKED)); |
| PasswordsModelDelegateFromWebContents(web_contents())->UpdatePassword( |
| password_form); |
| } |
| void ManagePasswordsBubbleModel::OnDoneClicked() { |
| - dismissal_reason_ = metrics_util::CLICKED_DONE; |
| + interaction_keeper_->set_dismissal_reason(metrics_util::CLICKED_DONE); |
| } |
| // TODO(gcasto): Is it worth having this be separate from OnDoneClicked()? |
| // User intent is pretty similar in both cases. |
| void ManagePasswordsBubbleModel::OnOKClicked() { |
| - dismissal_reason_ = metrics_util::CLICKED_OK; |
| + interaction_keeper_->set_dismissal_reason(metrics_util::CLICKED_OK); |
| } |
| void ManagePasswordsBubbleModel::OnManageLinkClicked() { |
| - dismissal_reason_ = metrics_util::CLICKED_MANAGE; |
| + interaction_keeper_->set_dismissal_reason(metrics_util::CLICKED_MANAGE); |
| if (GetSmartLockBrandingState(GetProfile()) == |
| password_bubble_experiment::SmartLockBranding::FULL) { |
| PasswordsModelDelegateFromWebContents(web_contents()) |
| @@ -295,22 +365,22 @@ void ManagePasswordsBubbleModel::OnManageLinkClicked() { |
| } |
| void ManagePasswordsBubbleModel::OnBrandLinkClicked() { |
| - dismissal_reason_ = metrics_util::CLICKED_BRAND_NAME; |
| + interaction_keeper_->set_dismissal_reason(metrics_util::CLICKED_BRAND_NAME); |
| PasswordsModelDelegateFromWebContents(web_contents()) |
| ->NavigateToSmartLockHelpPage(); |
| } |
| void ManagePasswordsBubbleModel::OnAutoSignInToastTimeout() { |
| - dismissal_reason_ = metrics_util::AUTO_SIGNIN_TOAST_TIMEOUT; |
| + interaction_keeper_->set_dismissal_reason( |
| + metrics_util::AUTO_SIGNIN_TOAST_TIMEOUT); |
| } |
| void ManagePasswordsBubbleModel::OnPasswordAction( |
| const autofill::PasswordForm& password_form, |
| PasswordAction action) { |
| - if (!web_contents()) |
| + Profile* profile = GetProfile(); |
| + if (!profile) |
| return; |
| - Profile* profile = |
| - Profile::FromBrowserContext(web_contents()->GetBrowserContext()); |
| password_manager::PasswordStore* password_store = |
| PasswordStoreFactory::GetForProfile( |
| profile, ServiceAccessType::EXPLICIT_ACCESS).get(); |
| @@ -341,6 +411,11 @@ bool ManagePasswordsBubbleModel::ShouldShowGoogleSmartLockWelcome() const { |
| return false; |
| } |
| +void ManagePasswordsBubbleModel::SetClockForTesting( |
| + std::unique_ptr<base::Clock> clock) { |
| + interaction_keeper_->SetClockForTesting(std::move(clock)); |
| +} |
| + |
| void ManagePasswordsBubbleModel::UpdatePendingStateTitle() { |
| title_brand_link_range_ = gfx::Range(); |
| PasswordTittleType type = |