summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVasilii Sukhanov <vasilii@chromium.org>2014-11-14 14:00:04 +0100
committerVasilii Sukhanov <vasilii@chromium.org>2014-11-14 13:01:24 +0000
commit1fa6c42cee1471df2a3f3e383472267f3819497a (patch)
treec3623a7766603b03322b6048079eb479bda49a99
parent120be7f3973f4acce516a993caf7d3ae74bc6ca7 (diff)
downloadchromium_src-1fa6c42cee1471df2a3f3e383472267f3819497a.zip
chromium_src-1fa6c42cee1471df2a3f3e383472267f3819497a.tar.gz
chromium_src-1fa6c42cee1471df2a3f3e383472267f3819497a.tar.bz2
Finch experiment for limiting the password bubble annoyance.
BUG=431739 TBR=battre@chromium.org,vabr@chromium.org Review URL: https://codereview.chromium.org/711043002 Cr-Commit-Position: refs/heads/master@{#303630} (cherry picked from commit 4d7cdf425d0b57f4c2a766433f5ffdc3cedd909f) Review URL: https://codereview.chromium.org/731473003 Cr-Commit-Position: refs/branch-heads/2214@{#41} Cr-Branched-From: 03655fd3f6d72165dc3c9bd2c89807305316fe6c-refs/heads/master@{#303346}
-rw-r--r--chrome/browser/prefs/browser_prefs.cc2
-rw-r--r--chrome/browser/ui/passwords/manage_passwords_bubble_model.cc16
-rw-r--r--chrome/browser/ui/passwords/manage_passwords_ui_controller.cc5
-rw-r--r--chrome/browser/ui/passwords/password_bubble_experiment.cc205
-rw-r--r--chrome/browser/ui/passwords/password_bubble_experiment.h57
-rw-r--r--chrome/browser/ui/passwords/password_bubble_experiment_unittest.cc139
-rw-r--r--chrome/chrome_browser_ui.gypi2
-rw-r--r--chrome/chrome_tests_unit.gypi1
-rw-r--r--chrome/common/pref_names.cc10
-rw-r--r--chrome/common/pref_names.h4
10 files changed, 441 insertions, 0 deletions
diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc
index 56bac85..6ea64ff 100644
--- a/chrome/browser/prefs/browser_prefs.cc
+++ b/chrome/browser/prefs/browser_prefs.cc
@@ -65,6 +65,7 @@
#include "chrome/browser/ui/browser_ui_prefs.h"
#include "chrome/browser/ui/navigation_correction_tab_observer.h"
#include "chrome/browser/ui/network_profile_bubble.h"
+#include "chrome/browser/ui/passwords/password_bubble_experiment.h"
#include "chrome/browser/ui/prefs/prefs_tab_helper.h"
#include "chrome/browser/ui/search_engines/keyword_editor_controller.h"
#include "chrome/browser/ui/startup/autolaunch_prompt.h"
@@ -407,6 +408,7 @@ void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry) {
MediaStreamDevicesController::RegisterProfilePrefs(registry);
NetPrefObserver::RegisterProfilePrefs(registry);
password_manager::PasswordManager::RegisterProfilePrefs(registry);
+ password_bubble_experiment::RegisterPrefs(registry);
PrefProxyConfigTrackerImpl::RegisterProfilePrefs(registry);
PrefsTabHelper::RegisterProfilePrefs(registry);
Profile::RegisterProfilePrefs(registry);
diff --git a/chrome/browser/ui/passwords/manage_passwords_bubble_model.cc b/chrome/browser/ui/passwords/manage_passwords_bubble_model.cc
index 1e1b433..dd59671 100644
--- a/chrome/browser/ui/passwords/manage_passwords_bubble_model.cc
+++ b/chrome/browser/ui/passwords/manage_passwords_bubble_model.cc
@@ -8,6 +8,7 @@
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/passwords/manage_passwords_ui_controller.h"
+#include "chrome/browser/ui/passwords/password_bubble_experiment.h"
#include "chrome/grit/generated_resources.h"
#include "components/password_manager/core/browser/password_store.h"
#include "components/password_manager/core/common/password_manager_ui.h"
@@ -33,6 +34,15 @@ int GetFieldWidth(FieldType type) {
: kPasswordFieldSize);
}
+void RecordExperimentStatistics(content::WebContents* web_contents,
+ metrics_util::UIDismissalReason reason) {
+ if (!web_contents)
+ return;
+ Profile* profile =
+ Profile::FromBrowserContext(web_contents->GetBrowserContext());
+ password_bubble_experiment::RecordBubbleClosed(profile->GetPrefs(), reason);
+}
+
} // namespace
ManagePasswordsBubbleModel::ManagePasswordsBubbleModel(
@@ -109,15 +119,20 @@ void ManagePasswordsBubbleModel::OnBubbleHidden() {
return;
metrics_util::LogUIDismissalReason(dismissal_reason_);
+ // Other use cases have been reported in the callbacks like OnSaveClicked().
+ if (dismissal_reason_ == metrics_util::NO_DIRECT_INTERACTION)
+ RecordExperimentStatistics(web_contents(), dismissal_reason_);
}
void ManagePasswordsBubbleModel::OnNopeClicked() {
dismissal_reason_ = metrics_util::CLICKED_NOPE;
+ RecordExperimentStatistics(web_contents(), dismissal_reason_);
state_ = password_manager::ui::PENDING_PASSWORD_STATE;
}
void ManagePasswordsBubbleModel::OnNeverForThisSiteClicked() {
dismissal_reason_ = metrics_util::CLICKED_NEVER;
+ RecordExperimentStatistics(web_contents(), dismissal_reason_);
ManagePasswordsUIController* manage_passwords_ui_controller =
ManagePasswordsUIController::FromWebContents(web_contents());
manage_passwords_ui_controller->NeverSavePassword();
@@ -134,6 +149,7 @@ void ManagePasswordsBubbleModel::OnUnblacklistClicked() {
void ManagePasswordsBubbleModel::OnSaveClicked() {
dismissal_reason_ = metrics_util::CLICKED_SAVE;
+ RecordExperimentStatistics(web_contents(), dismissal_reason_);
ManagePasswordsUIController* manage_passwords_ui_controller =
ManagePasswordsUIController::FromWebContents(web_contents());
manage_passwords_ui_controller->SavePassword();
diff --git a/chrome/browser/ui/passwords/manage_passwords_ui_controller.cc b/chrome/browser/ui/passwords/manage_passwords_ui_controller.cc
index 039f589..4afd320 100644
--- a/chrome/browser/ui/passwords/manage_passwords_ui_controller.cc
+++ b/chrome/browser/ui/passwords/manage_passwords_ui_controller.cc
@@ -15,6 +15,7 @@
#include "chrome/browser/ui/chrome_pages.h"
#include "chrome/browser/ui/location_bar/location_bar.h"
#include "chrome/browser/ui/passwords/manage_passwords_icon.h"
+#include "chrome/browser/ui/passwords/password_bubble_experiment.h"
#include "chrome/common/url_constants.h"
#include "components/password_manager/core/browser/password_store.h"
#include "content/public/browser/notification_service.h"
@@ -268,6 +269,10 @@ void ManagePasswordsUIController::ShowBubbleWithoutUserInteraction() {
Browser* browser = chrome::FindBrowserWithWebContents(web_contents());
if (!browser || browser->toolbar_model()->input_in_progress())
return;
+ if (state_ == password_manager::ui::PENDING_PASSWORD_AND_BUBBLE_STATE &&
+ !password_bubble_experiment::ShouldShowBubble(
+ browser->profile()->GetPrefs()))
+ return;
CommandUpdater* updater = browser->command_controller()->command_updater();
updater->ExecuteCommand(IDC_MANAGE_PASSWORDS_FOR_PAGE);
#endif
diff --git a/chrome/browser/ui/passwords/password_bubble_experiment.cc b/chrome/browser/ui/passwords/password_bubble_experiment.cc
new file mode 100644
index 0000000..276a038
--- /dev/null
+++ b/chrome/browser/ui/passwords/password_bubble_experiment.cc
@@ -0,0 +1,205 @@
+// Copyright 2014 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/password_bubble_experiment.h"
+
+#include "base/metrics/field_trial.h"
+#include "base/prefs/pref_service.h"
+#include "base/rand_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/time/time.h"
+#include "chrome/common/pref_names.h"
+#include "components/pref_registry/pref_registry_syncable.h"
+#include "components/variations/variations_associated_data.h"
+
+namespace password_bubble_experiment {
+namespace {
+
+bool IsNegativeEvent(password_manager::metrics_util::UIDismissalReason reason) {
+ return (reason == password_manager::metrics_util::NO_DIRECT_INTERACTION ||
+ reason == password_manager::metrics_util::CLICKED_NOPE ||
+ reason == password_manager::metrics_util::CLICKED_NEVER);
+}
+
+// "TimeSpan" experiment -----------------------------------------------------
+
+bool ExtractTimeSpanParams(base::TimeDelta* time_delta, int* nopes_limit) {
+ std::map<std::string, std::string> params;
+ bool retrieved = variations::GetVariationParams(kExperimentName, &params);
+ if (!retrieved)
+ return false;
+ int days = 0;
+ if (!base::StringToInt(params[kParamTimeSpan], &days) ||
+ !base::StringToInt(params[kParamTimeSpanNopeThreshold], nopes_limit))
+ return false;
+ *time_delta = base::TimeDelta::FromDays(days);
+ return true;
+}
+
+bool OverwriteTimeSpanPrefsIfNeeded(PrefService* prefs,
+ base::TimeDelta time_span) {
+ base::Time beginning = base::Time::FromInternalValue(
+ prefs->GetInt64(prefs::kPasswordBubbleTimeStamp));
+ base::Time now = base::Time::Now();
+ if (beginning + time_span < now) {
+ prefs->SetInt64(prefs::kPasswordBubbleTimeStamp, now.ToInternalValue());
+ prefs->SetInteger(prefs::kPasswordBubbleNopesCount, 0);
+ return true;
+ }
+ return false;
+}
+
+// If user dismisses the bubble >= kParamTimeSpanNopeThreshold times during
+// kParamTimeSpan days then the bubble isn't shown until the end of this time
+// span.
+bool ShouldShowBubbleTimeSpanExperiment(PrefService* prefs) {
+ base::TimeDelta time_span;
+ int nopes_limit = 0;
+ if (!ExtractTimeSpanParams(&time_span, &nopes_limit)) {
+ VLOG(2) << "Can't read parameters for "
+ << kExperimentName << " experiment";
+ return true;
+ }
+ // Check if the new time span has started.
+ if (OverwriteTimeSpanPrefsIfNeeded(prefs, time_span))
+ return true;
+ int current_nopes = prefs->GetInteger(prefs::kPasswordBubbleNopesCount);
+ return current_nopes < nopes_limit;
+}
+
+// Increase the "Nope" counter in prefs and start a new time span if needed.
+void UpdateTimeSpanPrefs(
+ PrefService* prefs,
+ password_manager::metrics_util::UIDismissalReason reason) {
+ if (!IsNegativeEvent(reason))
+ return;
+ base::TimeDelta time_span;
+ int nopes_limit = 0;
+ if (!ExtractTimeSpanParams(&time_span, &nopes_limit)) {
+ VLOG(2) << "Can't read parameters for "
+ << kExperimentName << " experiment";
+ return;
+ }
+ OverwriteTimeSpanPrefsIfNeeded(prefs, time_span);
+ int current_nopes = prefs->GetInteger(prefs::kPasswordBubbleNopesCount);
+ prefs->SetInteger(prefs::kPasswordBubbleNopesCount, current_nopes + 1);
+}
+
+// "Probability" experiment --------------------------------------------------
+
+bool ExtractProbabilityParams(unsigned* history_length, unsigned* saves) {
+ std::map<std::string, std::string> params;
+ bool retrieved = variations::GetVariationParams(kExperimentName, &params);
+ if (!retrieved)
+ return false;
+ return base::StringToUint(params[kParamProbabilityInteractionsCount],
+ history_length) &&
+ base::StringToUint(params[kParamProbabilityFakeSaves], saves);
+}
+
+std::vector<int> ReadInteractionHistory(PrefService* prefs) {
+ std::vector<int> interactions;
+ const base::ListValue* list =
+ prefs->GetList(prefs::kPasswordBubbleLastInteractions);
+ if (!list)
+ return interactions;
+ for (const base::Value* value : *list) {
+ int out_value;
+ if (value->GetAsInteger(&out_value))
+ interactions.push_back(out_value);
+ }
+ return interactions;
+}
+
+// We keep the history of last kParamProbabilityInteractionsCount interactions
+// with the bubble. We implicitly add kParamProbabilityFakeSaves "Save" clicks.
+// If there are x "Save" clicks among those kParamProbabilityInteractionsCount
+// then the bubble is shown with probability (x + kParamProbabilityFakeSaves)/
+// (kParamProbabilityInteractionsCount + kParamProbabilityFakeSaves).
+bool ShouldShowBubbleProbabilityExperiment(PrefService* prefs) {
+ unsigned history_length = 0, fake_saves = 0;
+ if (!ExtractProbabilityParams(&history_length, &fake_saves)) {
+ VLOG(2) << "Can't read parameters for "
+ << kExperimentName << " experiment";
+ return true;
+ }
+ std::vector<int> interactions = ReadInteractionHistory(prefs);
+ unsigned real_saves =
+ std::count(interactions.begin(), interactions.end(),
+ password_manager::metrics_util::CLICKED_SAVE);
+ return (interactions.size() + fake_saves) * base::RandDouble() <=
+ real_saves + fake_saves;
+}
+
+void UpdateProbabilityPrefs(
+ PrefService* prefs,
+ password_manager::metrics_util::UIDismissalReason reason) {
+ if (!IsNegativeEvent(reason) &&
+ reason != password_manager::metrics_util::CLICKED_SAVE)
+ return;
+ unsigned history_length = 0, fake_saves = 0;
+ if (!ExtractProbabilityParams(&history_length, &fake_saves)) {
+ VLOG(2) << "Can't read parameters for "
+ << kExperimentName << " experiment";
+ return;
+ }
+ std::vector<int> interactions = ReadInteractionHistory(prefs);
+ interactions.push_back(reason);
+ size_t history_beginning = interactions.size() > history_length ?
+ interactions.size() - history_length : 0;
+ base::ListValue value;
+ for (size_t i = history_beginning; i < interactions.size(); ++i)
+ value.AppendInteger(interactions[i]);
+ prefs->Set(prefs::kPasswordBubbleLastInteractions, value);
+}
+
+} // namespace
+
+const char kExperimentName[] = "PasswordBubbleAlgorithm";
+const char kGroupTimeSpanBased[] = "TimeSpan";
+const char kGroupProbabilityBased[] = "Probability";
+const char kParamProbabilityFakeSaves[] = "saves_count";
+const char kParamProbabilityInteractionsCount[] = "last_interactions_count";
+const char kParamTimeSpan[] = "time_span";
+const char kParamTimeSpanNopeThreshold[] = "nope_threshold";
+
+void RegisterPrefs(user_prefs::PrefRegistrySyncable* registry) {
+ registry->RegisterInt64Pref(
+ prefs::kPasswordBubbleTimeStamp,
+ 0,
+ user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
+ registry->RegisterIntegerPref(
+ prefs::kPasswordBubbleNopesCount,
+ 0,
+ user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
+ registry->RegisterListPref(
+ prefs::kPasswordBubbleLastInteractions,
+ user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
+}
+
+bool ShouldShowBubble(PrefService* prefs) {
+ if (!base::FieldTrialList::TrialExists(kExperimentName))
+ return true;
+ std::string group_name =
+ base::FieldTrialList::FindFullName(kExperimentName);
+
+ if (group_name == kGroupTimeSpanBased) {
+ return ShouldShowBubbleTimeSpanExperiment(prefs);
+ }
+ if (group_name == kGroupProbabilityBased) {
+ return ShouldShowBubbleProbabilityExperiment(prefs);
+ }
+
+ // The "Show Always" should be the default case.
+ return true;
+}
+
+void RecordBubbleClosed(
+ PrefService* prefs,
+ password_manager::metrics_util::UIDismissalReason reason) {
+ UpdateTimeSpanPrefs(prefs, reason);
+ UpdateProbabilityPrefs(prefs, reason);
+}
+
+} // namespace password_bubble_experiment
diff --git a/chrome/browser/ui/passwords/password_bubble_experiment.h b/chrome/browser/ui/passwords/password_bubble_experiment.h
new file mode 100644
index 0000000..9c4d302
--- /dev/null
+++ b/chrome/browser/ui/passwords/password_bubble_experiment.h
@@ -0,0 +1,57 @@
+// Copyright 2014 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_PASSWORD_BUBBLE_EXPERIMENT_H_
+#define CHROME_BROWSER_UI_PASSWORDS_PASSWORD_BUBBLE_EXPERIMENT_H_
+
+#include "base/macros.h"
+#include "components/password_manager/core/browser/password_manager_metrics_util.h"
+
+namespace user_prefs {
+class PrefRegistrySyncable;
+}
+
+class PrefService;
+
+// These functions handle the algorithms according to which the "Save password?"
+// bubble is shown to user.
+namespace password_bubble_experiment {
+
+void RegisterPrefs(user_prefs::PrefRegistrySyncable* registry);
+
+// The decision is made based on the "PasswordBubbleAlgorithm" finch experiment.
+// The default value is true.
+// It should be called before showing the "Save Password?" dialog.
+bool ShouldShowBubble(PrefService* prefs);
+
+// Should be called when user dismisses the "Save Password?" dialog. It stores
+// the statistics about interactions with the bubble.
+void RecordBubbleClosed(
+ PrefService* prefs,
+ password_manager::metrics_util::UIDismissalReason reason);
+
+// The name of the finch experiment controlling the algorithm.
+extern const char kExperimentName[];
+
+// The group name for the time based algorithm.
+extern const char kGroupTimeSpanBased[];
+
+// The group name for the probability algorithm.
+extern const char kGroupProbabilityBased[];
+
+// For "Probability" group. The additional "Saves" to be added to the model.
+extern const char kParamProbabilityFakeSaves[];
+
+// For "Probability" group. The interaction history length.
+extern const char kParamProbabilityInteractionsCount[];
+
+// For "TimeSpan" group. The time span until the nope counter is zeroed.
+extern const char kParamTimeSpan[];
+
+// For "TimeSpan" group. The nopes threshold.
+extern const char kParamTimeSpanNopeThreshold[];
+
+} // namespace password_bubble_experiment
+
+#endif // CHROME_BROWSER_UI_PASSWORDS_PASSWORD_BUBBLE_EXPERIMENT_H_
diff --git a/chrome/browser/ui/passwords/password_bubble_experiment_unittest.cc b/chrome/browser/ui/passwords/password_bubble_experiment_unittest.cc
new file mode 100644
index 0000000..96265b5
--- /dev/null
+++ b/chrome/browser/ui/passwords/password_bubble_experiment_unittest.cc
@@ -0,0 +1,139 @@
+// Copyright 2014 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/password_bubble_experiment.h"
+
+#include "base/files/scoped_temp_dir.h"
+#include "base/metrics/field_trial.h"
+#include "base/prefs/pref_service.h"
+#include "base/strings/string_number_conversions.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/test/base/testing_profile.h"
+#include "components/variations/entropy_provider.h"
+#include "components/variations/variations_associated_data.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+const int kTimeSpanDays = 2;
+const int kTimeSpanThreshold = 3;
+const int kProbabilityFakeSaves = 0;
+const int kProbabilityHistory = 10;
+
+void SetupTimeSpanExperiment() {
+ ASSERT_TRUE(base::FieldTrialList::CreateFieldTrial(
+ password_bubble_experiment::kExperimentName,
+ password_bubble_experiment::kGroupTimeSpanBased));
+ std::map<std::string, std::string> params;
+ params[password_bubble_experiment::kParamTimeSpan] =
+ base::IntToString(kTimeSpanDays);
+ params[password_bubble_experiment::kParamTimeSpanNopeThreshold] =
+ base::IntToString(kTimeSpanThreshold);
+ ASSERT_TRUE(variations::AssociateVariationParams(
+ password_bubble_experiment::kExperimentName,
+ password_bubble_experiment::kGroupTimeSpanBased,
+ params));
+}
+
+void SetupProbabilityExperiment() {
+ ASSERT_TRUE(base::FieldTrialList::CreateFieldTrial(
+ password_bubble_experiment::kExperimentName,
+ password_bubble_experiment::kGroupProbabilityBased));
+ std::map<std::string, std::string> params;
+ params[password_bubble_experiment::kParamProbabilityFakeSaves] =
+ base::IntToString(kProbabilityFakeSaves);
+ params[password_bubble_experiment::kParamProbabilityInteractionsCount] =
+ base::IntToString(kProbabilityHistory);
+ ASSERT_TRUE(variations::AssociateVariationParams(
+ password_bubble_experiment::kExperimentName,
+ password_bubble_experiment::kGroupProbabilityBased,
+ params));
+}
+
+} // namespace
+
+class PasswordBubbleExperimentTest : public testing::Test {
+ public:
+ void SetUp() override {
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+ profile_.reset(new TestingProfile(temp_dir_.path()));
+
+ field_trial_list_.reset(new base::FieldTrialList(
+ new metrics::SHA1EntropyProvider("foo")));
+ variations::testing::ClearAllVariationParams();
+ }
+
+ PrefService* prefs() { return profile_->GetPrefs(); }
+
+ private:
+ base::ScopedTempDir temp_dir_;
+ scoped_ptr<TestingProfile> profile_;
+ scoped_ptr<base::FieldTrialList> field_trial_list_;
+};
+
+TEST_F(PasswordBubbleExperimentTest, TimeSpan) {
+ SetupTimeSpanExperiment();
+
+ EXPECT_TRUE(password_bubble_experiment::ShouldShowBubble(prefs()));
+ // Don't save password enough times.
+ for (int i = 0; i < kTimeSpanThreshold; ++i) {
+ password_manager::metrics_util::UIDismissalReason reason = i % 2 ?
+ password_manager::metrics_util::NO_DIRECT_INTERACTION :
+ password_manager::metrics_util::CLICKED_NOPE;
+ password_bubble_experiment::RecordBubbleClosed(prefs(), reason);
+ }
+ EXPECT_FALSE(password_bubble_experiment::ShouldShowBubble(prefs()));
+
+ // Save password many times. It doesn't bring the bubble back while the time
+ // span isn't over.
+ for (int i = 0; i < 2*kTimeSpanThreshold; ++i) {
+ password_bubble_experiment::RecordBubbleClosed(
+ prefs(),
+ password_manager::metrics_util::CLICKED_SAVE);
+ }
+ EXPECT_FALSE(password_bubble_experiment::ShouldShowBubble(prefs()));
+}
+
+TEST_F(PasswordBubbleExperimentTest, TimeSpanOver) {
+ SetupTimeSpanExperiment();
+
+ base::Time past_interval =
+ base::Time::Now() - base::TimeDelta::FromDays(kTimeSpanDays + 1);
+ prefs()->SetInt64(prefs::kPasswordBubbleTimeStamp,
+ past_interval.ToInternalValue());
+ prefs()->SetInteger(prefs::kPasswordBubbleNopesCount, kTimeSpanThreshold);
+ // The time span is over. The bubble should be shown.
+ EXPECT_TRUE(password_bubble_experiment::ShouldShowBubble(prefs()));
+ EXPECT_EQ(0, prefs()->GetInteger(prefs::kPasswordBubbleNopesCount));
+
+ // Set the old time span again and record "Nope". The counter restarts from 0.
+ prefs()->SetInt64(prefs::kPasswordBubbleTimeStamp,
+ past_interval.ToInternalValue());
+ password_bubble_experiment::RecordBubbleClosed(
+ prefs(), password_manager::metrics_util::CLICKED_NOPE);
+ EXPECT_TRUE(password_bubble_experiment::ShouldShowBubble(prefs()));
+ EXPECT_EQ(1, prefs()->GetInteger(prefs::kPasswordBubbleNopesCount));
+}
+
+TEST_F(PasswordBubbleExperimentTest, Probability) {
+ SetupProbabilityExperiment();
+
+ EXPECT_TRUE(password_bubble_experiment::ShouldShowBubble(prefs()));
+ // Don't save password enough times.
+ for (int i = 0; i < kProbabilityHistory; ++i) {
+ password_manager::metrics_util::UIDismissalReason reason = i % 2 ?
+ password_manager::metrics_util::NO_DIRECT_INTERACTION :
+ password_manager::metrics_util::CLICKED_NOPE;
+ password_bubble_experiment::RecordBubbleClosed(prefs(), reason);
+ }
+ EXPECT_FALSE(password_bubble_experiment::ShouldShowBubble(prefs()));
+
+ // Save password enough times.
+ for (int i = 0; i < kProbabilityHistory; ++i) {
+ password_bubble_experiment::RecordBubbleClosed(
+ prefs(),
+ password_manager::metrics_util::CLICKED_SAVE);
+ }
+ EXPECT_TRUE(password_bubble_experiment::ShouldShowBubble(prefs()));
+}
diff --git a/chrome/chrome_browser_ui.gypi b/chrome/chrome_browser_ui.gypi
index c1730a2..81c6d34 100644
--- a/chrome/chrome_browser_ui.gypi
+++ b/chrome/chrome_browser_ui.gypi
@@ -804,6 +804,8 @@
'browser/ui/passwords/manage_passwords_icon.h',
'browser/ui/passwords/manage_passwords_ui_controller.cc',
'browser/ui/passwords/manage_passwords_ui_controller.h',
+ 'browser/ui/passwords/password_bubble_experiment.cc',
+ 'browser/ui/passwords/password_bubble_experiment.h',
'browser/ui/passwords/password_manager_presenter.cc',
'browser/ui/passwords/password_manager_presenter.h',
'browser/ui/passwords/password_ui_view.h',
diff --git a/chrome/chrome_tests_unit.gypi b/chrome/chrome_tests_unit.gypi
index f774021..a71b5f7 100644
--- a/chrome/chrome_tests_unit.gypi
+++ b/chrome/chrome_tests_unit.gypi
@@ -1145,6 +1145,7 @@
'browser/ui/passwords/manage_passwords_bubble_model_unittest.cc',
'browser/ui/passwords/manage_passwords_icon_mock.cc',
'browser/ui/passwords/manage_passwords_ui_controller_unittest.cc',
+ 'browser/ui/passwords/password_bubble_experiment_unittest.cc',
'browser/ui/passwords/password_manager_presenter_unittest.cc',
'browser/ui/search/instant_page_unittest.cc',
'browser/ui/search/instant_search_prerenderer_unittest.cc',
diff --git a/chrome/common/pref_names.cc b/chrome/common/pref_names.cc
index 2c2d7d4..fef4477 100644
--- a/chrome/common/pref_names.cc
+++ b/chrome/common/pref_names.cc
@@ -2279,4 +2279,14 @@ const char kBrowserAddPersonEnabled[] = "profile.add_person_enabled";
// A dictionary that maps user id to hardlock state.
const char kEasyUnlockHardlockState[] = "easy_unlock.hardlock_state";
+// The beginning of time span when we count user's "Nope" for the password
+// bubble.
+const char kPasswordBubbleTimeStamp[] = "password_bubble.timestamp";
+
+// The count of user's "Nope" for the password bubble.
+const char kPasswordBubbleNopesCount[] = "password_bubble.nopes";
+
+// Last user's interaction with the password bubble.
+const char kPasswordBubbleLastInteractions[] = "password_bubble.interactions";
+
} // namespace prefs
diff --git a/chrome/common/pref_names.h b/chrome/common/pref_names.h
index 01a0089..214a3b5 100644
--- a/chrome/common/pref_names.h
+++ b/chrome/common/pref_names.h
@@ -805,6 +805,10 @@ extern const char kBrowserAddPersonEnabled[];
extern const char kEasyUnlockHardlockState[];
+extern const char kPasswordBubbleTimeStamp[];
+extern const char kPasswordBubbleNopesCount[];
+extern const char kPasswordBubbleLastInteractions[];
+
} // namespace prefs
#endif // CHROME_COMMON_PREF_NAMES_H_