Commit 51dee544 authored by Dominic Battre's avatar Dominic Battre Committed by Commit Bot

UKMs for password save/update bubbles

This CL introduces measuring the events that save/update bubbles are triggered
and reasons why save/update bubbles are dismissed.

NOTRY=true

Bug: 732846
Change-Id: I885985e0ffcd610af8c2f0a1d55824d63330b43f
Reviewed-on: https://chromium-review.googlesource.com/565286
Commit-Queue: Dominic Battré <battre@chromium.org>
Reviewed-by: default avatarTatiana Gornak <melandory@chromium.org>
Reviewed-by: default avatarBalazs Engedy <engedy@chromium.org>
Reviewed-by: default avatarRobert Kaplow <rkaplow@chromium.org>
Reviewed-by: default avatarVasilii Sukhanov <vasilii@chromium.org>
Cr-Commit-Position: refs/heads/master@{#486699}
parent 84a21a7f
......@@ -288,10 +288,10 @@ bool ChromePasswordManagerClient::PromptUserToSaveOrUpdatePassword(
if (update_password) {
UpdatePasswordInfoBarDelegate::Create(web_contents(),
std::move(form_to_save));
return true;
} else {
SavePasswordInfoBarDelegate::Create(web_contents(),
std::move(form_to_save));
}
SavePasswordInfoBarDelegate::Create(web_contents(),
std::move(form_to_save));
#endif // !defined(OS_ANDROID)
return true;
}
......
......@@ -17,6 +17,7 @@
#include "components/infobars/core/infobar.h"
#include "components/infobars/core/infobar_manager.h"
#include "components/password_manager/core/browser/password_bubble_experiment.h"
#include "components/password_manager/core/browser/password_form_metrics_recorder.h"
#include "content/public/browser/web_contents.h"
#include "ui/base/l10n/l10n_util.h"
#include "url/origin.h"
......@@ -41,6 +42,7 @@ void SavePasswordInfoBarDelegate::Create(
SavePasswordInfoBarDelegate::~SavePasswordInfoBarDelegate() {
password_manager::metrics_util::LogUIDismissalReason(infobar_response_);
form_to_save_->metrics_recorder()->RecordUIDismissalReason(infobar_response_);
}
SavePasswordInfoBarDelegate::SavePasswordInfoBarDelegate(
......@@ -62,6 +64,10 @@ SavePasswordInfoBarDelegate::SavePasswordInfoBarDelegate(
&message, &message_link_range);
SetMessage(message);
SetMessageLinkRange(message_link_range);
form_to_save_->metrics_recorder()->RecordPasswordBubbleShown(
form_to_save_->GetCredentialSource(),
password_manager::metrics_util::AUTOMATIC_WITH_PASSWORD_PENDING);
}
infobars::InfoBarDelegate::InfoBarIdentifier
......
......@@ -21,10 +21,14 @@
#include "components/password_manager/core/browser/stub_password_manager_driver.h"
#include "components/password_manager/core/common/password_manager_pref_names.h"
#include "components/prefs/pref_service.h"
#include "components/ukm/test_ukm_recorder.h"
#include "components/ukm/ukm_source.h"
#include "content/public/browser/web_contents.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using password_manager::PasswordFormMetricsRecorder;
namespace {
class MockPasswordFormManager : public password_manager::PasswordFormManager {
......@@ -75,7 +79,8 @@ class SavePasswordInfoBarDelegateTest : public ChromeRenderViewHostTestHarness {
PrefService* prefs();
const autofill::PasswordForm& test_form() { return test_form_; }
std::unique_ptr<MockPasswordFormManager> CreateMockFormManager();
std::unique_ptr<MockPasswordFormManager> CreateMockFormManager(
scoped_refptr<PasswordFormMetricsRecorder> metrics_recorder);
protected:
std::unique_ptr<ConfirmInfoBarDelegate> CreateDelegate(
......@@ -99,7 +104,6 @@ SavePasswordInfoBarDelegateTest::SavePasswordInfoBarDelegateTest()
test_form_.origin = GURL("http://example.com");
test_form_.username_value = base::ASCIIToUTF16("username");
test_form_.password_value = base::ASCIIToUTF16("12345");
fetcher_.Fetch();
}
PrefService* SavePasswordInfoBarDelegateTest::prefs() {
......@@ -109,11 +113,15 @@ PrefService* SavePasswordInfoBarDelegateTest::prefs() {
}
std::unique_ptr<MockPasswordFormManager>
SavePasswordInfoBarDelegateTest::CreateMockFormManager() {
SavePasswordInfoBarDelegateTest::CreateMockFormManager(
scoped_refptr<PasswordFormMetricsRecorder> metrics_recorder) {
auto manager = base::MakeUnique<MockPasswordFormManager>(
&password_manager_, &client_, driver_.AsWeakPtr(), test_form(),
&fetcher_);
manager->Init(nullptr);
manager->Init(metrics_recorder);
manager->ProvisionallySave(
test_form(),
password_manager::PasswordFormManager::ALLOW_OTHER_POSSIBLE_USERNAMES);
return manager;
}
......@@ -137,7 +145,7 @@ void SavePasswordInfoBarDelegateTest::TearDown() {
TEST_F(SavePasswordInfoBarDelegateTest, CancelTestCredentialSourceAPI) {
std::unique_ptr<MockPasswordFormManager> password_form_manager(
CreateMockFormManager());
CreateMockFormManager(nullptr));
EXPECT_CALL(*password_form_manager.get(), PermanentlyBlacklist());
std::unique_ptr<ConfirmInfoBarDelegate> infobar(
CreateDelegate(std::move(password_form_manager)));
......@@ -147,9 +155,88 @@ TEST_F(SavePasswordInfoBarDelegateTest, CancelTestCredentialSourceAPI) {
TEST_F(SavePasswordInfoBarDelegateTest,
CancelTestCredentialSourcePasswordManager) {
std::unique_ptr<MockPasswordFormManager> password_form_manager(
CreateMockFormManager());
CreateMockFormManager(nullptr));
EXPECT_CALL(*password_form_manager.get(), PermanentlyBlacklist());
std::unique_ptr<ConfirmInfoBarDelegate> infobar(
CreateDelegate(std::move(password_form_manager)));
EXPECT_TRUE(infobar->Cancel());
}
class SavePasswordInfoBarDelegateTestForUKMs
: public SavePasswordInfoBarDelegateTest,
public ::testing::WithParamInterface<
PasswordFormMetricsRecorder::BubbleDismissalReason> {
public:
SavePasswordInfoBarDelegateTestForUKMs() = default;
~SavePasswordInfoBarDelegateTestForUKMs() = default;
};
// Verify that URL keyed metrics are recorded for showing and interacting with
// the password save prompt.
TEST_P(SavePasswordInfoBarDelegateTestForUKMs, VerifyUKMRecording) {
using BubbleTrigger = PasswordFormMetricsRecorder::BubbleTrigger;
using BubbleDismissalReason =
PasswordFormMetricsRecorder::BubbleDismissalReason;
BubbleDismissalReason dismissal_reason = GetParam();
SCOPED_TRACE(::testing::Message() << "dismissal_reason = "
<< static_cast<int64_t>(dismissal_reason));
ukm::TestUkmRecorder test_ukm_recorder;
{
// Setup metrics recorder
ukm::SourceId source_id = test_ukm_recorder.GetNewSourceID();
static_cast<ukm::UkmRecorder*>(&test_ukm_recorder)
->UpdateSourceURL(source_id, GURL("https://www.example.com/"));
auto recorder = base::MakeRefCounted<PasswordFormMetricsRecorder>(
true /*is_main_frame_secure*/,
PasswordFormMetricsRecorder::CreateUkmEntryBuilder(&test_ukm_recorder,
source_id));
// Exercise delegate.
std::unique_ptr<MockPasswordFormManager> password_form_manager(
CreateMockFormManager(recorder));
if (dismissal_reason == BubbleDismissalReason::kDeclined)
EXPECT_CALL(*password_form_manager.get(), PermanentlyBlacklist());
std::unique_ptr<ConfirmInfoBarDelegate> infobar(
CreateDelegate(std::move(password_form_manager)));
switch (dismissal_reason) {
case BubbleDismissalReason::kAccepted:
EXPECT_TRUE(infobar->Accept());
break;
case BubbleDismissalReason::kDeclined:
EXPECT_TRUE(infobar->Cancel());
break;
case BubbleDismissalReason::kIgnored:
// Do nothing.
break;
case BubbleDismissalReason::kUnknown:
NOTREACHED();
break;
}
}
// Verify metrics.
const ukm::UkmSource* source =
test_ukm_recorder.GetSourceForUrl("https://www.example.com/");
ASSERT_TRUE(source);
test_ukm_recorder.ExpectMetric(
*source, "PasswordForm",
password_manager::internal::kUkmSavingPromptShown, 1);
test_ukm_recorder.ExpectMetric(
*source, "PasswordForm",
password_manager::internal::kUkmSavingPromptTrigger,
static_cast<int64_t>(BubbleTrigger::kPasswordManagerSuggestionAutomatic));
test_ukm_recorder.ExpectMetric(
*source, "PasswordForm",
password_manager::internal::kUkmSavingPromptInteraction,
static_cast<int64_t>(dismissal_reason));
}
INSTANTIATE_TEST_CASE_P(
/*no extra name*/,
SavePasswordInfoBarDelegateTestForUKMs,
::testing::Values(
PasswordFormMetricsRecorder::BubbleDismissalReason::kAccepted,
PasswordFormMetricsRecorder::BubbleDismissalReason::kDeclined,
PasswordFormMetricsRecorder::BubbleDismissalReason::kIgnored));
......@@ -17,6 +17,7 @@
#include "components/browser_sync/profile_sync_service.h"
#include "components/infobars/core/infobar.h"
#include "components/password_manager/core/browser/password_bubble_experiment.h"
#include "components/password_manager/core/browser/password_form_metrics_recorder.h"
#include "components/strings/grit/components_strings.h"
#include "content/public/browser/web_contents.h"
#include "ui/base/l10n/l10n_util.h"
......@@ -36,7 +37,10 @@ void UpdatePasswordInfoBarDelegate::Create(
is_smartlock_branding_enabled))));
}
UpdatePasswordInfoBarDelegate::~UpdatePasswordInfoBarDelegate() {}
UpdatePasswordInfoBarDelegate::~UpdatePasswordInfoBarDelegate() {
passwords_state_.form_manager()->metrics_recorder()->RecordUIDismissalReason(
infobar_response_);
}
base::string16 UpdatePasswordInfoBarDelegate::GetBranding() const {
return l10n_util::GetStringUTF16(is_smartlock_branding_enabled_
......@@ -61,7 +65,8 @@ UpdatePasswordInfoBarDelegate::UpdatePasswordInfoBarDelegate(
content::WebContents* web_contents,
std::unique_ptr<password_manager::PasswordFormManager> form_to_update,
bool is_smartlock_branding_enabled)
: is_smartlock_branding_enabled_(is_smartlock_branding_enabled) {
: infobar_response_(password_manager::metrics_util::NO_DIRECT_INTERACTION),
is_smartlock_branding_enabled_(is_smartlock_branding_enabled) {
base::string16 message;
gfx::Range message_link_range = gfx::Range();
GetSavePasswordDialogTitleTextAndLinkRange(
......@@ -72,6 +77,10 @@ UpdatePasswordInfoBarDelegate::UpdatePasswordInfoBarDelegate(
SetMessageLinkRange(message_link_range);
// TODO(melandory): Add histograms, crbug.com/577129
form_to_update->metrics_recorder()->RecordPasswordBubbleShown(
form_to_update->GetCredentialSource(),
password_manager::metrics_util::AUTOMATIC_WITH_PASSWORD_PENDING_UPDATE);
passwords_state_.set_client(
ChromePasswordManagerClient::FromWebContents(web_contents));
passwords_state_.OnUpdatePassword(std::move(form_to_update));
......@@ -91,7 +100,12 @@ base::string16 UpdatePasswordInfoBarDelegate::GetButtonLabel(
return l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_UPDATE_BUTTON);
}
void UpdatePasswordInfoBarDelegate::InfoBarDismissed() {
infobar_response_ = password_manager::metrics_util::CLICKED_CANCEL;
}
bool UpdatePasswordInfoBarDelegate::Accept() {
infobar_response_ = password_manager::metrics_util::CLICKED_SAVE;
UpdatePasswordInfoBar* update_password_infobar =
static_cast<UpdatePasswordInfoBar*>(infobar());
password_manager::PasswordFormManager* form_manager =
......@@ -106,3 +120,8 @@ bool UpdatePasswordInfoBarDelegate::Accept() {
}
return true;
}
bool UpdatePasswordInfoBarDelegate::Cancel() {
infobar_response_ = password_manager::metrics_util::CLICKED_CANCEL;
return true;
}
......@@ -12,6 +12,7 @@
#include "chrome/browser/password_manager/password_manager_infobar_delegate_android.h"
#include "chrome/browser/ui/passwords/manage_passwords_state.h"
#include "components/password_manager/core/browser/password_form_manager.h"
#include "components/password_manager/core/browser/password_manager_metrics_util.h"
namespace content {
class WebContents;
......@@ -57,11 +58,16 @@ class UpdatePasswordInfoBarDelegate : public PasswordManagerInfoBarDelegate {
std::unique_ptr<password_manager::PasswordFormManager> form_to_update,
bool is_smartlock_branding_enabled);
// Used to track the results we get from the info bar.
password_manager::metrics_util::UIDismissalReason infobar_response_;
// ConfirmInfoBarDelegate:
infobars::InfoBarDelegate::InfoBarIdentifier GetIdentifier() const override;
int GetButtons() const override;
base::string16 GetButtonLabel(InfoBarButton button) const override;
bool Accept() override;
void InfoBarDismissed() override;
bool Cancel() override;
ManagePasswordsState passwords_state_;
base::string16 branding_;
......
......@@ -35,6 +35,8 @@ void ManagePasswordsControllerTest::SetUp() {
ui_controller_.reset(new testing::NiceMock<PasswordsModelDelegateMock>);
ON_CALL(*ui_controller_, GetWebContents())
.WillByDefault(Return(test_web_contents_.get()));
ON_CALL(*ui_controller_, GetPasswordFormMetricsRecorder())
.WillByDefault(Return(nullptr));
PasswordStoreFactory::GetInstance()->SetTestingFactoryAndUse(
profile(), password_manager::BuildPasswordStore<
content::BrowserContext,
......
......@@ -22,6 +22,7 @@
#include "chrome/grit/generated_resources.h"
#include "components/browser_sync/profile_sync_service.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_constants.h"
#include "components/password_manager/core/browser/password_store.h"
#include "components/password_manager/core/common/password_manager_pref_names.h"
......@@ -110,11 +111,16 @@ class ManagePasswordsBubbleModel::InteractionKeeper {
}
private:
static password_manager::metrics_util::UIDismissalReason
ToNearestUIDismissalReason(
password_manager::metrics_util::UpdatePasswordSubmissionEvent
update_password_submission_event);
// The way the bubble appeared.
const password_manager::metrics_util::UIDisplayDisposition
display_disposition_;
// Dismissal reason for a bubble.
// Dismissal reason for a save bubble. Not filled for update bubbles.
password_manager::metrics_util::UIDismissalReason dismissal_reason_;
// Dismissal reason for the update bubble.
......@@ -216,6 +222,51 @@ void ManagePasswordsBubbleModel::InteractionKeeper::ReportInteractions(
if (update_password_submission_event_ != metrics_util::NO_UPDATE_SUBMISSION)
LogUpdatePasswordSubmissionEvent(update_password_submission_event_);
}
// Record UKM statistics on dismissal reason.
if (model->delegate_ && model->delegate_->GetPasswordFormMetricsRecorder()) {
if (model->state() != password_manager::ui::PENDING_PASSWORD_UPDATE_STATE) {
model->delegate_->GetPasswordFormMetricsRecorder()
->RecordUIDismissalReason(dismissal_reason_);
} else {
model->delegate_->GetPasswordFormMetricsRecorder()
->RecordUIDismissalReason(
ToNearestUIDismissalReason(update_password_submission_event_));
}
}
}
// static
password_manager::metrics_util::UIDismissalReason
ManagePasswordsBubbleModel::InteractionKeeper::ToNearestUIDismissalReason(
password_manager::metrics_util::UpdatePasswordSubmissionEvent
update_password_submission_event) {
switch (update_password_submission_event) {
case password_manager::metrics_util::NO_ACCOUNTS_CLICKED_UPDATE:
case password_manager::metrics_util::ONE_ACCOUNT_CLICKED_UPDATE:
case password_manager::metrics_util::MULTIPLE_ACCOUNTS_CLICKED_UPDATE:
case password_manager::metrics_util::PASSWORD_OVERRIDDEN_CLICKED_UPDATE:
return password_manager::metrics_util::CLICKED_SAVE;
case password_manager::metrics_util::NO_ACCOUNTS_CLICKED_NOPE:
case password_manager::metrics_util::ONE_ACCOUNT_CLICKED_NOPE:
case password_manager::metrics_util::MULTIPLE_ACCOUNTS_CLICKED_NOPE:
case password_manager::metrics_util::PASSWORD_OVERRIDDEN_CLICKED_NOPE:
return password_manager::metrics_util::CLICKED_CANCEL;
case password_manager::metrics_util::NO_ACCOUNTS_NO_INTERACTION:
case password_manager::metrics_util::ONE_ACCOUNT_NO_INTERACTION:
case password_manager::metrics_util::MULTIPLE_ACCOUNTS_NO_INTERACTION:
case password_manager::metrics_util::PASSWORD_OVERRIDDEN_NO_INTERACTION:
case password_manager::metrics_util::NO_UPDATE_SUBMISSION:
return password_manager::metrics_util::NO_DIRECT_INTERACTION;
case password_manager::metrics_util::UPDATE_PASSWORD_EVENT_COUNT:
// Not reached.
break;
}
NOTREACHED();
return password_manager::metrics_util::NO_DIRECT_INTERACTION;
}
ManagePasswordsBubbleModel::ManagePasswordsBubbleModel(
......@@ -318,6 +369,11 @@ ManagePasswordsBubbleModel::ManagePasswordsBubbleModel(
break;
}
}
if (delegate_ && delegate_->GetPasswordFormMetricsRecorder()) {
delegate_->GetPasswordFormMetricsRecorder()->RecordPasswordBubbleShown(
delegate_->GetCredentialSource(), display_disposition);
}
metrics_util::LogUIDisplayDisposition(display_disposition);
interaction_keeper_.reset(new InteractionKeeper(std::move(interaction_stats),
display_disposition));
......
......@@ -9,6 +9,7 @@
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_samples.h"
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/histogram_tester.h"
#include "base/test/simple_test_clock.h"
......@@ -19,6 +20,7 @@
#include "chrome/test/base/testing_profile.h"
#include "components/browser_sync/profile_sync_service_mock.h"
#include "components/password_manager/core/browser/mock_password_store.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_manager_test_utils.h"
#include "components/password_manager/core/browser/statistics_table.h"
......@@ -26,9 +28,12 @@
#include "components/password_manager/core/common/password_manager_pref_names.h"
#include "components/password_manager/core/common/password_manager_ui.h"
#include "components/prefs/pref_service.h"
#include "components/ukm/test_ukm_recorder.h"
#include "components/ukm/ukm_source.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "content/public/test/web_contents_tester.h"
#include "services/metrics/public/cpp/ukm_recorder.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
......@@ -104,7 +109,9 @@ class ManagePasswordsBubbleModelTest : public ::testing::Test {
void SetUp() override {
test_web_contents_.reset(
content::WebContentsTester::CreateTestWebContents(&profile_, nullptr));
mock_delegate_.reset(new testing::StrictMock<PasswordsModelDelegateMock>);
mock_delegate_.reset(new PasswordsModelDelegateMock);
ON_CALL(*mock_delegate_, GetPasswordFormMetricsRecorder())
.WillByDefault(Return(nullptr));
PasswordStoreFactory::GetInstance()->SetTestingFactoryAndUse(
profile(),
password_manager::BuildPasswordStore<
......@@ -279,7 +286,7 @@ TEST_F(ManagePasswordsBubbleModelTest, ClickNever) {
EXPECT_CALL(*controller(), SavePassword()).Times(0);
EXPECT_CALL(*controller(), NeverSavePassword());
model()->OnNeverForThisSiteClicked();
EXPECT_EQ(password_manager::ui::PENDING_PASSWORD_STATE, model()->state());
EXPECT_EQ(password_manager::ui::PENDING_PASSWORD_STATE, model()->state());
DestroyModelExpectReason(password_manager::metrics_util::CLICKED_NEVER);
}
......@@ -473,3 +480,117 @@ INSTANTIATE_TEST_CASE_P(Default,
ManagePasswordsBubbleModelManageLinkTest,
::testing::Values(TestSyncService::SyncedTypes::ALL,
TestSyncService::SyncedTypes::NONE));
// Verify that URL keyed metrics are properly recorded.
TEST_F(ManagePasswordsBubbleModelTest, RecordUKMs) {
using BubbleDismissalReason =
password_manager::PasswordFormMetricsRecorder::BubbleDismissalReason;
using BubbleTrigger =
password_manager::PasswordFormMetricsRecorder::BubbleTrigger;
using password_manager::metrics_util::CredentialSourceType;
// |credential_management_api| defines whether credentials originate from the
// credential management API.
for (const bool credential_management_api : {false, true}) {
// |update| defines whether this is an update or a save bubble.
for (const bool update : {false, true}) {
for (const auto interaction :
{BubbleDismissalReason::kAccepted, BubbleDismissalReason::kDeclined,
BubbleDismissalReason::kIgnored}) {
SCOPED_TRACE(testing::Message()
<< "update = " << update
<< ", interaction = " << static_cast<int64_t>(interaction)
<< ", credential management api ="
<< credential_management_api);
ukm::TestUkmRecorder test_ukm_recorder;
{
// Setup metrics recorder
ukm::SourceId source_id = test_ukm_recorder.GetNewSourceID();
static_cast<ukm::UkmRecorder*>(&test_ukm_recorder)
->UpdateSourceURL(source_id, GURL("https://www.example.com/"));
auto recorder = base::MakeRefCounted<
password_manager::PasswordFormMetricsRecorder>(
true /*is_main_frame_secure*/,
password_manager::PasswordFormMetricsRecorder::
CreateUkmEntryBuilder(&test_ukm_recorder, source_id));
// Exercise bubble.
ON_CALL(*controller(), GetPasswordFormMetricsRecorder())
.WillByDefault(Return(recorder.get()));
ON_CALL(*controller(), GetCredentialSource())
.WillByDefault(
Return(credential_management_api
? CredentialSourceType::kCredentialManagementAPI
: CredentialSourceType::kPasswordManager));
if (update)
PretendUpdatePasswordWaiting();
else
PretendPasswordWaiting();
if (interaction == BubbleDismissalReason::kAccepted && update) {
autofill::PasswordForm form;
EXPECT_CALL(*controller(), UpdatePassword(form));
model()->OnUpdateClicked(form);
} else if (interaction == BubbleDismissalReason::kAccepted &&
!update) {
EXPECT_CALL(*GetStore(),
RemoveSiteStatsImpl(GURL(kSiteOrigin).GetOrigin()));
EXPECT_CALL(*controller(), SavePassword());
model()->OnSaveClicked();
} else if (interaction == BubbleDismissalReason::kDeclined &&
update) {
EXPECT_CALL(*controller(), SavePassword()).Times(0);
model()->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();
} else if (interaction == BubbleDismissalReason::kIgnored && update) {
EXPECT_CALL(*controller(), SavePassword()).Times(0);
EXPECT_CALL(*controller(), 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);
} else {
NOTREACHED();
}
DestroyModel();
}
// Flush async calls on password store.
base::RunLoop().RunUntilIdle();
ASSERT_TRUE(testing::Mock::VerifyAndClearExpectations(controller()));
ASSERT_TRUE(testing::Mock::VerifyAndClearExpectations(GetStore()));
// Verify metrics.
const ukm::UkmSource* source =
test_ukm_recorder.GetSourceForUrl("https://www.example.com/");
ASSERT_TRUE(source);
test_ukm_recorder.ExpectMetric(
*source, "PasswordForm",
update ? password_manager::internal::kUkmUpdatingPromptShown
: password_manager::internal::kUkmSavingPromptShown,
1);
test_ukm_recorder.ExpectMetric(
*source, "PasswordForm",
update ? password_manager::internal::kUkmUpdatingPromptTrigger
: password_manager::internal::kUkmSavingPromptTrigger,
static_cast<int64_t>(
credential_management_api
? BubbleTrigger::kCredentialManagementAPIAutomatic
: BubbleTrigger::kPasswordManagerSuggestionAutomatic));
test_ukm_recorder.ExpectMetric(
*source, "PasswordForm",
update ? password_manager::internal::kUkmUpdatingPromptInteraction
: password_manager::internal::kUkmSavingPromptInteraction,
static_cast<int64_t>(interaction));
}
}
}
}
......@@ -208,6 +208,15 @@ const GURL& ManagePasswordsUIController::GetOrigin() const {
return passwords_data_.origin();
}
password_manager::PasswordFormMetricsRecorder*
ManagePasswordsUIController::GetPasswordFormMetricsRecorder() {
// The form manager may be null for example for auto sign-in toasts of the
// credential manager API.
password_manager::PasswordFormManager* form_manager =
passwords_data_.form_manager();
return form_manager ? form_manager->metrics_recorder() : nullptr;
}
password_manager::ui::State ManagePasswordsUIController::GetState() const {
return passwords_data_.state();
}
......@@ -226,6 +235,15 @@ const autofill::PasswordForm& ManagePasswordsUIController::
return form_manager->pending_credentials();
}
password_manager::metrics_util::CredentialSourceType
ManagePasswordsUIController::GetCredentialSource() const {
password_manager::PasswordFormManager* form_manager =
passwords_data_.form_manager();
return form_manager
? form_manager->GetCredentialSource()
: password_manager::metrics_util::CredentialSourceType::kUnknown;
}
bool ManagePasswordsUIController::IsPasswordOverridden() const {
const password_manager::PasswordFormManager* form_manager =
passwords_data_.form_manager();
......
......@@ -85,8 +85,12 @@ class ManagePasswordsUIController
// PasswordsModelDelegate:
content::WebContents* GetWebContents() const override;
const GURL& GetOrigin() const override;
password_manager::PasswordFormMetricsRecorder*
GetPasswordFormMetricsRecorder() override;
password_manager::ui::State GetState() const override;
const autofill::PasswordForm& GetPendingPassword() const override;
password_manager::metrics_util::CredentialSourceType GetCredentialSource()
const override;
bool IsPasswordOverridden() const override;
const std::vector<std::unique_ptr<autofill::PasswordForm>>& GetCurrentForms()
const override;
......
......@@ -19,7 +19,11 @@ class WebContents;
}
namespace password_manager {
struct InteractionsStats;
}
class PasswordFormMetricsRecorder;
namespace metrics_util {
enum class CredentialSourceType;
} // namespace metrics_util
} // namespace password_manager
class GURL;
// An interface for ManagePasswordsBubbleModel implemented by
......@@ -30,6 +34,12 @@ class PasswordsModelDelegate {
// Returns WebContents* the model is attached to.
virtual content::WebContents* GetWebContents() const = 0;
// Returns the password_manager::PasswordFormMetricsRecorder that is
// associated with the PasswordFormManager that governs the password being
// submitted.
virtual password_manager::PasswordFormMetricsRecorder*
GetPasswordFormMetricsRecorder() = 0;
// Returns the URL of the site the current forms are retrieved for.
virtual const GURL& GetOrigin() const = 0;
......@@ -41,6 +51,10 @@ class PasswordsModelDelegate {
// the returned credential in AUTO_SIGNIN_STATE.
virtual const autofill::PasswordForm& GetPendingPassword() const = 0;
// Returns the source of the credential to be saved.
virtual password_manager::metrics_util::CredentialSourceType
GetCredentialSource() const = 0;
// True if the password for previously stored account was overridden, i.e. in
// newly submitted form the password is different from stored one.
virtual bool IsPasswordOverridden() const = 0;
......
......@@ -18,9 +18,13 @@ class PasswordsModelDelegateMock
~PasswordsModelDelegateMock() override;
MOCK_CONST_METHOD0(GetWebContents, content::WebContents*());
MOCK_METHOD0(GetPasswordFormMetricsRecorder,
password_manager::PasswordFormMetricsRecorder*());
MOCK_CONST_METHOD0(GetOrigin, const GURL&());
MOCK_CONST_METHOD0(GetState, password_manager::ui::State());
MOCK_CONST_METHOD0(GetPendingPassword, const autofill::PasswordForm&());
MOCK_CONST_METHOD0(GetCredentialSource,
password_manager::metrics_util::CredentialSourceType());
MOCK_CONST_METHOD0(IsPasswordOverridden, bool());
MOCK_CONST_METHOD0(
GetCurrentForms,
......
......@@ -68,6 +68,11 @@ void CredentialManagerPasswordFormManager::ProcessMatches(
weak_factory_.GetWeakPtr()));
}
metrics_util::CredentialSourceType
CredentialManagerPasswordFormManager::GetCredentialSource() {
return metrics_util::CredentialSourceType::kCredentialManagementAPI;
}
void CredentialManagerPasswordFormManager::NotifyDelegate() {
delegate_->OnProvisionalSaveComplete();
}
......
......@@ -52,6 +52,8 @@ class CredentialManagerPasswordFormManager : public PasswordFormManager {
const std::vector<const autofill::PasswordForm*>& non_federated,
size_t filtered_count) override;
metrics_util::CredentialSourceType GetCredentialSource() override;
private:
// Calls OnProvisionalSaveComplete on |delegate_|.
void NotifyDelegate();
......
......@@ -75,4 +75,18 @@ TEST_F(CredentialManagerPasswordFormManagerTest, AbortEarly) {
EXPECT_FALSE(form_manager);
}
// Ensure that GetCredentialSource is actually overriden and returns the proper
// value.
TEST_F(CredentialManagerPasswordFormManagerTest, GetCredentialSource) {
PasswordForm observed_form;
MockDelegate delegate;
auto form_manager = base::MakeUnique<CredentialManagerPasswordFormManager>(
&client_, driver_.AsWeakPtr(), observed_form,
base::MakeUnique<PasswordForm>(observed_form), &delegate,
base::MakeUnique<StubFormSaver>(), base::MakeUnique<FakeFormFetcher>());
form_manager->Init(nullptr);
ASSERT_EQ(metrics_util::CredentialSourceType::kCredentialManagementAPI,
form_manager->GetCredentialSource());
}
} // namespace password_manager
......@@ -1361,6 +1361,10 @@ std::unique_ptr<PasswordFormManager> PasswordFormManager::Clone() {
return result;
}
metrics_util::CredentialSourceType PasswordFormManager::GetCredentialSource() {
return metrics_util::CredentialSourceType::kPasswordManager;
}
void PasswordFormManager::SendVotesOnSave() {
if (observed_form_.IsPossibleChangePasswordFormWithoutUsername())
return;
......
......@@ -24,6 +24,7 @@
#include "components/password_manager/core/browser/password_form_metrics_recorder.h"
#include "components/password_manager/core/browser/password_form_user_action.h"
#include "components/password_manager/core/browser/password_manager_driver.h"
#include "components/password_manager/core/browser/password_manager_metrics_util.h"
#include "components/password_manager/core/browser/password_store.h"
using autofill::FormData;
......@@ -275,6 +276,10 @@ class PasswordFormManager : public FormFetcher::Consumer {
// adds itself as a consumer of the new one.
void GrabFetcher(std::unique_ptr<FormFetcher> fetcher);
PasswordFormMetricsRecorder* metrics_recorder() {
return metrics_recorder_.get();
}
// Create a copy of |*this| which can be passed to the code handling
// save-password related UI. This omits some parts of the internal data, so
// the result is not identical to the original.
......@@ -282,6 +287,11 @@ class PasswordFormManager : public FormFetcher::Consumer {
// another one.
std::unique_ptr<PasswordFormManager> Clone();
// Returns who created this PasswordFormManager. The Credential Management API
// uses a derived class of the PasswordFormManager that can indicate its
// origin.
virtual metrics_util::CredentialSourceType GetCredentialSource();
protected:
// FormFetcher::Consumer:
void ProcessMatches(
......
......@@ -56,6 +56,9 @@ PasswordFormMetricsRecorder::~PasswordFormMetricsRecorder() {
RecordUkmMetric(internal::kUkmSubmissionFormType, submitted_form_type_);
}
RecordUkmMetric(internal::kUkmUpdatingPromptShown, update_prompt_shown_);
RecordUkmMetric(internal::kUkmSavingPromptShown, save_prompt_shown_);
}
// static
......@@ -223,6 +226,110 @@ void PasswordFormMetricsRecorder::RecordHistogramsOnSuppressedAccounts(
RecordUkmMetric("SuppressedAccount.Manual.SameOrganizationName", best_match);
}
void PasswordFormMetricsRecorder::RecordPasswordBubbleShown(
metrics_util::CredentialSourceType credential_source_type,
metrics_util::UIDisplayDisposition display_disposition) {
if (credential_source_type == metrics_util::CredentialSourceType::kUnknown)
return;
BubbleTrigger automatic_trigger_type =
credential_source_type ==
metrics_util::CredentialSourceType::kPasswordManager
? BubbleTrigger::kPasswordManagerSuggestionAutomatic
: BubbleTrigger::kCredentialManagementAPIAutomatic;
BubbleTrigger manual_trigger_type =
credential_source_type ==
metrics_util::CredentialSourceType::kPasswordManager
? BubbleTrigger::kPasswordManagerSuggestionManual
: BubbleTrigger::kCredentialManagementAPIManual;
switch (display_disposition) {
// New credential cases:
case metrics_util::AUTOMATIC_WITH_PASSWORD_PENDING:
save_prompt_shown_ = true;
RecordUkmMetric(internal::kUkmSavingPromptTrigger,
static_cast<int64_t>(automatic_trigger_type));
break;
case metrics_util::MANUAL_WITH_PASSWORD_PENDING:
save_prompt_shown_ = true;
RecordUkmMetric(internal::kUkmSavingPromptTrigger,
static_cast<int64_t>(manual_trigger_type));
break;
// Update cases:
case metrics_util::AUTOMATIC_WITH_PASSWORD_PENDING_UPDATE:
update_prompt_shown_ = true;
RecordUkmMetric(internal::kUkmUpdatingPromptTrigger,
static_cast<int64_t>(automatic_trigger_type));
break;
case metrics_util::MANUAL_WITH_PASSWORD_PENDING_UPDATE:
update_prompt_shown_ = true;
RecordUkmMetric(internal::kUkmUpdatingPromptTrigger,
static_cast<int64_t>(manual_trigger_type));
break;
// Other reasons to show a bubble:
case metrics_util::MANUAL_MANAGE_PASSWORDS:
case metrics_util::AUTOMATIC_GENERATED_PASSWORD_CONFIRMATION:
case metrics_util::AUTOMATIC_SIGNIN_TOAST:
// Do nothing.
return;
// Obsolte display dispositions:
case metrics_util::MANUAL_BLACKLISTED_OBSOLETE:
case metrics_util::AUTOMATIC_CREDENTIAL_REQUEST_OBSOLETE:
case metrics_util::NUM_DISPLAY_DISPOSITIONS:
NOTREACHED();
return;
}
}
void PasswordFormMetricsRecorder::RecordUIDismissalReason(
metrics_util::UIDismissalReason ui_dismissal_reason) {
DCHECK(!(update_prompt_shown_ && save_prompt_shown_));
if (!(update_prompt_shown_ || save_prompt_shown_))
return;
const char* metric = update_prompt_shown_
? internal::kUkmUpdatingPromptInteraction
: internal::kUkmSavingPromptInteraction;
switch (ui_dismissal_reason) {
// Accepted by user.
case metrics_util::CLICKED_SAVE:
RecordUkmMetric(metric,
static_cast<int64_t>(BubbleDismissalReason::kAccepted));
break;
// Declined by user.
case metrics_util::CLICKED_CANCEL:
case metrics_util::CLICKED_NEVER:
RecordUkmMetric(metric,
static_cast<int64_t>(BubbleDismissalReason::kDeclined));
break;
// Ignored by user.
case metrics_util::NO_DIRECT_INTERACTION:
RecordUkmMetric(metric,
static_cast<int64_t>(BubbleDismissalReason::kIgnored));
break;
// Ignore these for metrics collection:
case metrics_util::CLICKED_MANAGE:
case metrics_util::CLICKED_DONE:
case metrics_util::CLICKED_OK:
case metrics_util::CLICKED_BRAND_NAME:
case metrics_util::CLICKED_PASSWORDS_DASHBOARD:
case metrics_util::AUTO_SIGNIN_TOAST_TIMEOUT:
break;
// These should not reach here:
case metrics_util::CLICKED_UNBLACKLIST_OBSOLETE:
case metrics_util::CLICKED_CREDENTIAL_OBSOLETE:
case metrics_util::AUTO_SIGNIN_TOAST_CLICKED_OBSOLETE:
case metrics_util::NUM_UI_RESPONSES:
NOTREACHED();
break;
}
}
void PasswordFormMetricsRecorder::RecordFillEvent(ManagerAutofillEvent event) {
RecordUkmMetric(internal::kUkmManagerFillEvent, event);
}
......
......@@ -14,11 +14,13 @@
#include "base/memory/ref_counted.h"
#include "components/autofill/core/common/password_form.h"
#include "components/password_manager/core/browser/password_form_user_action.h"
#include "components/password_manager/core/browser/password_manager_metrics_util.h"
#include "services/metrics/public/cpp/ukm_recorder.h"
namespace password_manager {
// Internal namespace is intended for component wide access only.
// Internal namespace is intended for component wide access and unit testing
// only.
namespace internal {
// UKM Metric names. Exposed in internal namespace for unittesting.
......@@ -36,6 +38,33 @@ constexpr char kUkmSubmissionResult[] = "Submission.SubmissionResult";
// Note that no metric is recorded for kSubmittedFormTypeUnspecified.
constexpr char kUkmSubmissionFormType[] = "Submission.SubmittedFormType";
// This metric records the boolean value indicating whether a password update
// prompt was shown, which asked the user for permission to update a password.
constexpr char kUkmUpdatingPromptShown[] = "Updating.Prompt.Shown";
// This metric records the reason why a password update prompt was shown to ask
// the user for permission to update a password. The values correspond to
// PasswordFormMetricsRecorder::BubbleTrigger.
constexpr char kUkmUpdatingPromptTrigger[] = "Updating.Prompt.Trigger";
// This metric records how a user interacted with an updating prompt. The values
// correspond to PasswordFormMetricsRecorder::BubbleDismissalReason.
constexpr char kUkmUpdatingPromptInteraction[] = "Updating.Prompt.Interaction";
// This metric records the boolean value indicating whether a password save
// prompt was shown, which asked the user for permission to save a new
// credential.
constexpr char kUkmSavingPromptShown[] = "Saving.Prompt.Shown";
// This metric records the reason why a password save prompt was shown to ask
// the user for permission to save a new credential. The values correspond to
// PasswordFormMetricsRecorder::BubbleTrigger.
constexpr char kUkmSavingPromptTrigger[] = "Saving.Prompt.Trigger";
// This metric records how a user interacted with a saving prompt. The values
// correspond to PasswordFormMetricsRecorder::BubbleDismissalReason.
constexpr char kUkmSavingPromptInteraction[] = "Saving.Prompt.Interaction";
// This metric records attempts to fill a password form. Values correspond to
// PasswordFormMetricsRecorder::ManagerFillEvent.
constexpr char kUkmManagerFillEvent[] = "ManagerFill.Action";
......@@ -147,6 +176,28 @@ class PasswordFormMetricsRecorder
kSubmittedFormTypeMax
};
// The reason why a password bubble was shown on the screen.
enum class BubbleTrigger {
kUnknown = 0,
// The password manager suggests the user to save a password and asks for
// confirmation.
kPasswordManagerSuggestionAutomatic,
kPasswordManagerSuggestionManual,
// The site asked the user to save a password via the credential management
// API.
kCredentialManagementAPIAutomatic,
kCredentialManagementAPIManual,
kMax,
};
// The reason why a password bubble was dismissed.
enum class BubbleDismissalReason {
kUnknown = 0,
kAccepted = 1,
kDeclined = 2,
kIgnored = 3
};
// The maximum number of combinations of the ManagerAction, UserAction and
// SubmitResult enums.
// This is used when recording the actions taken by the form in UMA.
......@@ -194,6 +245,15 @@ class PasswordFormMetricsRecorder
const FormFetcher& form_fetcher,
const autofill::PasswordForm& pending_credentials);
// Records the event that a password bubble was shown.
void RecordPasswordBubbleShown(
metrics_util::CredentialSourceType credential_source_type,
metrics_util::UIDisplayDisposition display_disposition);
// Records the dismissal of a password bubble.
void RecordUIDismissalReason(
metrics_util::UIDismissalReason ui_dismissal_reason);
// Records that the password manager managed or failed to fill a form.
void RecordFillEvent(ManagerAutofillEvent event);
......@@ -249,6 +309,12 @@ class PasswordFormMetricsRecorder
// Whether this form has an auto generated password.
bool has_generated_password_ = false;
// Whether the user was shown a prompt to update a password.
bool update_prompt_shown_ = false;
// Whether the user was shown a prompt to save a new credential.
bool save_prompt_shown_ = false;
// These three fields record the "ActionsTaken" by the browser and
// the user with this form, and the result. They are combined and
// recorded in UMA when the PasswordFormMetricsRecorder is destroyed.
......
......@@ -31,6 +31,7 @@ std::unique_ptr<ukm::UkmEntryBuilder> CreateUkmEntryBuilder(
source_id);
}
// TODO(crbug.com/738921) Replace this with generalized infrastructure.
// Verifies that the metric |metric_name| was recorded with value |value| in the
// single entry of |test_ukm_recorder_| exactly |expected_count| times.
void ExpectUkmValueCount(ukm::TestUkmRecorder* test_ukm_recorder,
......@@ -382,4 +383,142 @@ TEST(PasswordFormMetricsRecorder, SubmittedFormType) {
}
}
TEST(PasswordFormMetricsRecorder, RecordPasswordBubbleShown) {
using Trigger = PasswordFormMetricsRecorder::BubbleTrigger;
static constexpr struct {
// Stimuli:
metrics_util::CredentialSourceType credential_source_type;
metrics_util::UIDisplayDisposition display_disposition;
// Expectations:
const char* expected_trigger_metric;
Trigger expected_trigger_value;
bool expected_save_prompt_shown;
bool expected_update_prompt_shown;
} kTests[] = {
// Source = PasswordManager, Saving.
{metrics_util::CredentialSourceType::kPasswordManager,
metrics_util::AUTOMATIC_WITH_PASSWORD_PENDING,
internal::kUkmSavingPromptTrigger,
Trigger::kPasswordManagerSuggestionAutomatic, true, false},
{metrics_util::CredentialSourceType::kPasswordManager,
metrics_util::MANUAL_WITH_PASSWORD_PENDING,
internal::kUkmSavingPromptTrigger,
Trigger::kPasswordManagerSuggestionManual, true, false},
// Source = PasswordManager, Updating.
{metrics_util::CredentialSourceType::kPasswordManager,
metrics_util::AUTOMATIC_WITH_PASSWORD_PENDING_UPDATE,
internal::kUkmUpdatingPromptTrigger,
Trigger::kPasswordManagerSuggestionAutomatic, false, true},
{metrics_util::CredentialSourceType::kPasswordManager,
metrics_util::MANUAL_WITH_PASSWORD_PENDING_UPDATE,
internal::kUkmUpdatingPromptTrigger,
Trigger::kPasswordManagerSuggestionManual, false, true},
// Source = Credential Management API, Saving.
{metrics_util::CredentialSourceType::kCredentialManagementAPI,
metrics_util::AUTOMATIC_WITH_PASSWORD_PENDING,
internal::kUkmSavingPromptTrigger,
Trigger::kCredentialManagementAPIAutomatic, true, false},
{metrics_util::CredentialSourceType::kCredentialManagementAPI,
metrics_util::MANUAL_WITH_PASSWORD_PENDING,
internal::kUkmSavingPromptTrigger,
Trigger::kCredentialManagementAPIManual, true, false},
// Source = Credential Management API, Updating.
{metrics_util::CredentialSourceType::kCredentialManagementAPI,
metrics_util::AUTOMATIC_WITH_PASSWORD_PENDING_UPDATE,
internal::kUkmUpdatingPromptTrigger,
Trigger::kCredentialManagementAPIAutomatic, false, true},
{metrics_util::CredentialSourceType::kCredentialManagementAPI,
metrics_util::MANUAL_WITH_PASSWORD_PENDING_UPDATE,
internal::kUkmUpdatingPromptTrigger,
Trigger::kCredentialManagementAPIManual, false, true},
// Source = Unknown, Saving.
{metrics_util::CredentialSourceType::kUnknown,
metrics_util::AUTOMATIC_WITH_PASSWORD_PENDING,
internal::kUkmSavingPromptTrigger,
Trigger::kPasswordManagerSuggestionAutomatic, false, false},
};
for (const auto& test : kTests) {
SCOPED_TRACE(testing::Message()
<< "credential_source_type = "
<< static_cast<int64_t>(test.credential_source_type)
<< ", display_disposition = " << test.display_disposition);
ukm::TestUkmRecorder test_ukm_recorder;
{
auto recorder = base::MakeRefCounted<PasswordFormMetricsRecorder>(
true /*is_main_frame_secure*/,
CreateUkmEntryBuilder(&test_ukm_recorder));
recorder->RecordPasswordBubbleShown(test.credential_source_type,
test.display_disposition);
}
// Verify data
const ukm::UkmSource* source = test_ukm_recorder.GetSourceForUrl(kTestUrl);
ASSERT_TRUE(source);
if (test.credential_source_type !=
metrics_util::CredentialSourceType::kUnknown) {
test_ukm_recorder.ExpectMetric(
*source, "PasswordForm", test.expected_trigger_metric,
static_cast<int64_t>(test.expected_trigger_value));
} else {
EXPECT_FALSE(test_ukm_recorder.HasMetric(
*source, "PasswordForm", internal::kUkmSavingPromptTrigger));
EXPECT_FALSE(test_ukm_recorder.HasMetric(
*source, "PasswordForm", internal::kUkmUpdatingPromptTrigger));
}
test_ukm_recorder.ExpectMetric(*source, "PasswordForm",
internal::kUkmSavingPromptShown,
test.expected_save_prompt_shown);
test_ukm_recorder.ExpectMetric(*source, "PasswordForm",
internal::kUkmUpdatingPromptShown,
test.expected_update_prompt_shown);
}
}
TEST(PasswordFormMetricsRecorder, RecordUIDismissalReason) {
static constexpr struct {
// Stimuli:
metrics_util::UIDisplayDisposition display_disposition;
metrics_util::UIDismissalReason dismissal_reason;
// Expectations:
const char* expected_trigger_metric;
PasswordFormMetricsRecorder::BubbleDismissalReason expected_metric_value;
} kTests[] = {
{metrics_util::AUTOMATIC_WITH_PASSWORD_PENDING,
metrics_util::CLICKED_SAVE, internal::kUkmSavingPromptInteraction,
PasswordFormMetricsRecorder::BubbleDismissalReason::kAccepted},
{metrics_util::MANUAL_WITH_PASSWORD_PENDING, metrics_util::CLICKED_CANCEL,
internal::kUkmSavingPromptInteraction,
PasswordFormMetricsRecorder::BubbleDismissalReason::kDeclined},
{metrics_util::AUTOMATIC_WITH_PASSWORD_PENDING_UPDATE,
metrics_util::CLICKED_NEVER, internal::kUkmUpdatingPromptInteraction,
PasswordFormMetricsRecorder::BubbleDismissalReason::kDeclined},
{metrics_util::MANUAL_WITH_PASSWORD_PENDING_UPDATE,
metrics_util::NO_DIRECT_INTERACTION,
internal::kUkmUpdatingPromptInteraction,
PasswordFormMetricsRecorder::BubbleDismissalReason::kIgnored},
};
for (const auto& test : kTests) {
SCOPED_TRACE(testing::Message()
<< "display_disposition = " << test.display_disposition
<< ", dismissal_reason = " << test.dismissal_reason);
ukm::TestUkmRecorder test_ukm_recorder;
{
auto recorder = base::MakeRefCounted<PasswordFormMetricsRecorder>(
true /*is_main_frame_secure*/,
CreateUkmEntryBuilder(&test_ukm_recorder));
recorder->RecordPasswordBubbleShown(
metrics_util::CredentialSourceType::kPasswordManager,
test.display_disposition);
recorder->RecordUIDismissalReason(test.dismissal_reason);
}
// Verify data
const ukm::UkmSource* source = test_ukm_recorder.GetSourceForUrl(kTestUrl);
ASSERT_TRUE(source);
test_ukm_recorder.ExpectMetric(
*source, "PasswordForm", test.expected_trigger_metric,
static_cast<int64_t>(test.expected_metric_value));
}
}
} // namespace password_manager
......@@ -27,6 +27,7 @@ std::unique_ptr<ukm::UkmEntryBuilder> CreateUkmEntryBuilder(
test_ukm_recorder, source_id);
}
// TODO(crbug.com/738921) Replace this with generalized infrastructure.
// Verifies that the metric |metric_name| was recorded with value |value| in the
// single entry of |test_ukm_recorder| exactly |expected_count| times.
void ExpectUkmValueCount(ukm::TestUkmRecorder* test_ukm_recorder,
......
......@@ -208,6 +208,17 @@ enum ReauthToAccessPasswordInSettingsEvent {
REAUTH_COUNT
};
// Specifies the type of PasswordFormManagers and derived classes to distinguish
// the context in which a PasswordFormManager is being created and used.
enum class CredentialSourceType {
kUnknown,
// This is used for form based credential management (PasswordFormManager).
kPasswordManager,
// This is used for credential management API based credential management
// (CredentialManagerPasswordFormManager).
kCredentialManagementAPI
};
#if defined(OS_WIN) || (defined(OS_MACOSX) && !defined(OS_IOS)) || \
(defined(OS_LINUX) && !defined(OS_CHROMEOS))
enum class SyncPasswordHashChange {
......
......@@ -41,6 +41,10 @@ class IOSChromePasswordManagerInfoBarDelegate : public ConfirmInfoBarDelegate {
infobar_response_ = response;
}
password_manager::metrics_util::UIDismissalReason infobar_response() const {
return infobar_response_;
}
private:
// ConfirmInfoBarDelegate implementation.
Type GetInfoBarType() const override;
......
......@@ -34,13 +34,20 @@ void IOSChromeSavePasswordInfoBarDelegate::Create(
infobar_manager->CreateConfirmInfoBar(std::move(delegate)));
}
IOSChromeSavePasswordInfoBarDelegate::~IOSChromeSavePasswordInfoBarDelegate() {}
IOSChromeSavePasswordInfoBarDelegate::~IOSChromeSavePasswordInfoBarDelegate() {
form_to_save()->metrics_recorder()->RecordUIDismissalReason(
infobar_response());
}
IOSChromeSavePasswordInfoBarDelegate::IOSChromeSavePasswordInfoBarDelegate(
bool is_smart_lock_branding_enabled,
std::unique_ptr<PasswordFormManager> form_to_save)
std::unique_ptr<PasswordFormManager> form_manager)
: IOSChromePasswordManagerInfoBarDelegate(is_smart_lock_branding_enabled,
std::move(form_to_save)) {}
std::move(form_manager)) {
form_to_save()->metrics_recorder()->RecordPasswordBubbleShown(
form_to_save()->GetCredentialSource(),
password_manager::metrics_util::AUTOMATIC_WITH_PASSWORD_PENDING);
}
infobars::InfoBarDelegate::InfoBarIdentifier
IOSChromeSavePasswordInfoBarDelegate::GetIdentifier() const {
......
......@@ -61,6 +61,7 @@ class IOSChromeUpdatePasswordInfoBarDelegate
int GetButtons() const override;
base::string16 GetButtonLabel(InfoBarButton button) const override;
bool Accept() override;
bool Cancel() override;
// The credential that should be displayed in the infobar, and for which the
// password will be updated.
......
......@@ -42,7 +42,10 @@ void IOSChromeUpdatePasswordInfoBarDelegate::Create(
}
IOSChromeUpdatePasswordInfoBarDelegate::
~IOSChromeUpdatePasswordInfoBarDelegate() {}
~IOSChromeUpdatePasswordInfoBarDelegate() {
form_to_save()->metrics_recorder()->RecordUIDismissalReason(
infobar_response());
}
IOSChromeUpdatePasswordInfoBarDelegate::IOSChromeUpdatePasswordInfoBarDelegate(
bool is_smart_lock_branding_enabled,
......@@ -50,6 +53,9 @@ IOSChromeUpdatePasswordInfoBarDelegate::IOSChromeUpdatePasswordInfoBarDelegate(
: IOSChromePasswordManagerInfoBarDelegate(is_smart_lock_branding_enabled,
std::move(form_manager)) {
selected_account_ = form_to_save()->preferred_match()->username_value;
form_to_save()->metrics_recorder()->RecordPasswordBubbleShown(
form_to_save()->GetCredentialSource(),
password_manager::metrics_util::AUTOMATIC_WITH_PASSWORD_PENDING_UPDATE);
}
bool IOSChromeUpdatePasswordInfoBarDelegate::ShowMultipleAccounts() const {
......@@ -106,5 +112,12 @@ bool IOSChromeUpdatePasswordInfoBarDelegate::Accept() {
} else {
form_to_save()->Update(form_to_save()->pending_credentials());
}
set_infobar_response(password_manager::metrics_util::CLICKED_SAVE);
return true;
}
bool IOSChromeUpdatePasswordInfoBarDelegate::Cancel() {
DCHECK(form_to_save());
set_infobar_response(password_manager::metrics_util::CLICKED_CANCEL);
return true;
}
......@@ -708,6 +708,28 @@ be describing additional metrics about the same event.
enum ManagerAutofillEvent.
</summary>
</metric>
<metric name="Saving.Prompt.Interaction">
<summary>
Records how the user interacted with a saving prompt. Recorded values
correspond to the enum PasswordFormMetricsRecorder::BubbleDismissalReason.
</summary>
</metric>
<metric name="Saving.Prompt.Shown">
<summary>
Records, for each password form seen by the password manager, whether the
user was shown a prompt that asked for permission to save new credentials.
In case a store() via the Credential Management API, a virtual password
form is created, for which this metric is recorded. Recorded values are 0
and 1 for false and true.
</summary>
</metric>
<metric name="Saving.Prompt.Trigger">
<summary>
Records the trigger of each password save bubble that was shown to the
user to ask for permission to save new credentials. Recorded values
correspond to the enum PasswordFormMetricsRecorder::BubbleTrigger.
</summary>
</metric>
<metric name="Submission.Observed">
<summary>
Records whether a submission of a password form has been observered. The
......@@ -808,6 +830,26 @@ be describing additional metrics about the same event.
enum SuppressedAccountExistence.
</summary>
</metric>
<metric name="Updating.Prompt.Interaction">
<summary>
Records how the user interacted with an updating prompt. Recorded values
correspond to the enum PasswordFormMetricsRecorder::BubbleDismissalReason.
</summary>
</metric>
<metric name="Updating.Prompt.Shown">
<summary>
Records, for each password form seen by the password manager, whether the
user was shown a prompt that asked for permission to update existing
credentials. Recorded values are 0 and 1 for false and true.
</summary>
</metric>
<metric name="Updating.Prompt.Trigger">
<summary>
Records the trigger of each password update bubble that was shown to the
user to ask for permission to update existing credentials. Recorded values
correspond to the enum PasswordFormMetricsRecorder::BubbleTrigger.
</summary>
</metric>
</event>
<event name="PageWithPassword">
......
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