| OLD | NEW |
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "chrome/browser/ui/passwords/password_bubble_experiment.h" | 5 #include "chrome/browser/ui/passwords/password_bubble_experiment.h" |
| 6 | 6 |
| 7 #include "base/metrics/field_trial.h" | 7 #include "base/metrics/field_trial.h" |
| 8 #include "base/prefs/pref_service.h" | 8 #include "base/prefs/pref_service.h" |
| 9 #include "base/rand_util.h" | |
| 10 #include "base/strings/string_number_conversions.h" | 9 #include "base/strings/string_number_conversions.h" |
| 11 #include "base/time/time.h" | |
| 12 #include "chrome/common/pref_names.h" | 10 #include "chrome/common/pref_names.h" |
| 13 #include "components/pref_registry/pref_registry_syncable.h" | 11 #include "components/pref_registry/pref_registry_syncable.h" |
| 14 #include "components/variations/variations_associated_data.h" | 12 #include "components/variations/variations_associated_data.h" |
| 15 | 13 |
| 16 namespace password_bubble_experiment { | 14 namespace password_bubble_experiment { |
| 17 namespace { | 15 namespace { |
| 18 | 16 |
| 19 bool IsNegativeEvent(password_manager::metrics_util::UIDismissalReason reason) { | 17 // Return the number of consecutive times the user clicked "Not now" in the |
| 20 return (reason == password_manager::metrics_util::NO_DIRECT_INTERACTION || | 18 // "Save password" bubble. |
| 21 reason == password_manager::metrics_util::CLICKED_NOPE || | 19 int GetCurrentNopesCount(PrefService* prefs) { |
| 22 reason == password_manager::metrics_util::CLICKED_NEVER); | 20 return prefs->GetInteger(prefs::kPasswordBubbleNopesCount); |
| 23 } | |
| 24 | |
| 25 // "TimeSpan" experiment ----------------------------------------------------- | |
| 26 | |
| 27 bool ExtractTimeSpanParams(base::TimeDelta* time_delta, int* nopes_limit) { | |
| 28 std::map<std::string, std::string> params; | |
| 29 bool retrieved = variations::GetVariationParams(kExperimentName, ¶ms); | |
| 30 if (!retrieved) | |
| 31 return false; | |
| 32 int days = 0; | |
| 33 if (!base::StringToInt(params[kParamTimeSpan], &days) || | |
| 34 !base::StringToInt(params[kParamTimeSpanNopeThreshold], nopes_limit)) | |
| 35 return false; | |
| 36 *time_delta = base::TimeDelta::FromDays(days); | |
| 37 return true; | |
| 38 } | |
| 39 | |
| 40 bool OverwriteTimeSpanPrefsIfNeeded(PrefService* prefs, | |
| 41 base::TimeDelta time_span) { | |
| 42 base::Time beginning = base::Time::FromInternalValue( | |
| 43 prefs->GetInt64(prefs::kPasswordBubbleTimeStamp)); | |
| 44 base::Time now = base::Time::Now(); | |
| 45 if (beginning + time_span < now) { | |
| 46 prefs->SetInt64(prefs::kPasswordBubbleTimeStamp, now.ToInternalValue()); | |
| 47 prefs->SetInteger(prefs::kPasswordBubbleNopesCount, 0); | |
| 48 return true; | |
| 49 } | |
| 50 return false; | |
| 51 } | |
| 52 | |
| 53 // If user dismisses the bubble >= kParamTimeSpanNopeThreshold times during | |
| 54 // kParamTimeSpan days then the bubble isn't shown until the end of this time | |
| 55 // span. | |
| 56 bool ShouldShowBubbleTimeSpanExperiment(PrefService* prefs) { | |
| 57 base::TimeDelta time_span; | |
| 58 int nopes_limit = 0; | |
| 59 if (!ExtractTimeSpanParams(&time_span, &nopes_limit)) { | |
| 60 VLOG(2) << "Can't read parameters for " | |
| 61 << kExperimentName << " experiment"; | |
| 62 return true; | |
| 63 } | |
| 64 // Check if the new time span has started. | |
| 65 if (OverwriteTimeSpanPrefsIfNeeded(prefs, time_span)) | |
| 66 return true; | |
| 67 int current_nopes = prefs->GetInteger(prefs::kPasswordBubbleNopesCount); | |
| 68 return current_nopes < nopes_limit; | |
| 69 } | |
| 70 | |
| 71 // Increase the "Nope" counter in prefs and start a new time span if needed. | |
| 72 void UpdateTimeSpanPrefs( | |
| 73 PrefService* prefs, | |
| 74 password_manager::metrics_util::UIDismissalReason reason) { | |
| 75 if (!IsNegativeEvent(reason)) | |
| 76 return; | |
| 77 base::TimeDelta time_span; | |
| 78 int nopes_limit = 0; | |
| 79 if (!ExtractTimeSpanParams(&time_span, &nopes_limit)) { | |
| 80 VLOG(2) << "Can't read parameters for " | |
| 81 << kExperimentName << " experiment"; | |
| 82 return; | |
| 83 } | |
| 84 OverwriteTimeSpanPrefsIfNeeded(prefs, time_span); | |
| 85 int current_nopes = prefs->GetInteger(prefs::kPasswordBubbleNopesCount); | |
| 86 prefs->SetInteger(prefs::kPasswordBubbleNopesCount, current_nopes + 1); | |
| 87 } | |
| 88 | |
| 89 // "Probability" experiment -------------------------------------------------- | |
| 90 | |
| 91 bool ExtractProbabilityParams(unsigned* history_length, unsigned* saves) { | |
| 92 std::map<std::string, std::string> params; | |
| 93 bool retrieved = variations::GetVariationParams(kExperimentName, ¶ms); | |
| 94 if (!retrieved) | |
| 95 return false; | |
| 96 return base::StringToUint(params[kParamProbabilityInteractionsCount], | |
| 97 history_length) && | |
| 98 base::StringToUint(params[kParamProbabilityFakeSaves], saves); | |
| 99 } | |
| 100 | |
| 101 std::vector<int> ReadInteractionHistory(PrefService* prefs) { | |
| 102 std::vector<int> interactions; | |
| 103 const base::ListValue* list = | |
| 104 prefs->GetList(prefs::kPasswordBubbleLastInteractions); | |
| 105 if (!list) | |
| 106 return interactions; | |
| 107 for (const base::Value* value : *list) { | |
| 108 int out_value; | |
| 109 if (value->GetAsInteger(&out_value)) | |
| 110 interactions.push_back(out_value); | |
| 111 } | |
| 112 return interactions; | |
| 113 } | |
| 114 | |
| 115 // We keep the history of last kParamProbabilityInteractionsCount interactions | |
| 116 // with the bubble. We implicitly add kParamProbabilityFakeSaves "Save" clicks. | |
| 117 // If there are x "Save" clicks among those kParamProbabilityInteractionsCount | |
| 118 // then the bubble is shown with probability (x + kParamProbabilityFakeSaves)/ | |
| 119 // (kParamProbabilityInteractionsCount + kParamProbabilityFakeSaves). | |
| 120 bool ShouldShowBubbleProbabilityExperiment(PrefService* prefs) { | |
| 121 unsigned history_length = 0, fake_saves = 0; | |
| 122 if (!ExtractProbabilityParams(&history_length, &fake_saves)) { | |
| 123 VLOG(2) << "Can't read parameters for " | |
| 124 << kExperimentName << " experiment"; | |
| 125 return true; | |
| 126 } | |
| 127 std::vector<int> interactions = ReadInteractionHistory(prefs); | |
| 128 unsigned real_saves = | |
| 129 std::count(interactions.begin(), interactions.end(), | |
| 130 password_manager::metrics_util::CLICKED_SAVE); | |
| 131 return (interactions.size() + fake_saves) * base::RandDouble() <= | |
| 132 real_saves + fake_saves; | |
| 133 } | |
| 134 | |
| 135 void UpdateProbabilityPrefs( | |
| 136 PrefService* prefs, | |
| 137 password_manager::metrics_util::UIDismissalReason reason) { | |
| 138 if (!IsNegativeEvent(reason) && | |
| 139 reason != password_manager::metrics_util::CLICKED_SAVE) | |
| 140 return; | |
| 141 unsigned history_length = 0, fake_saves = 0; | |
| 142 if (!ExtractProbabilityParams(&history_length, &fake_saves)) { | |
| 143 VLOG(2) << "Can't read parameters for " | |
| 144 << kExperimentName << " experiment"; | |
| 145 return; | |
| 146 } | |
| 147 std::vector<int> interactions = ReadInteractionHistory(prefs); | |
| 148 interactions.push_back(reason); | |
| 149 size_t history_beginning = interactions.size() > history_length ? | |
| 150 interactions.size() - history_length : 0; | |
| 151 base::ListValue value; | |
| 152 for (size_t i = history_beginning; i < interactions.size(); ++i) | |
| 153 value.AppendInteger(interactions[i]); | |
| 154 prefs->Set(prefs::kPasswordBubbleLastInteractions, value); | |
| 155 } | 21 } |
| 156 | 22 |
| 157 } // namespace | 23 } // namespace |
| 158 | 24 |
| 159 const char kExperimentName[] = "PasswordBubbleAlgorithm"; | 25 const char kExperimentName[] = "PasswordBubbleAlgorithm"; |
| 160 const char kGroupTimeSpanBased[] = "TimeSpan"; | 26 const char kParamNopeThreshold[] = "consecutive_nope_threshold"; |
| 161 const char kGroupProbabilityBased[] = "Probability"; | |
| 162 const char kParamProbabilityFakeSaves[] = "saves_count"; | |
| 163 const char kParamProbabilityInteractionsCount[] = "last_interactions_count"; | |
| 164 const char kParamTimeSpan[] = "time_span"; | |
| 165 const char kParamTimeSpanNopeThreshold[] = "nope_threshold"; | |
| 166 | 27 |
| 167 void RegisterPrefs(user_prefs::PrefRegistrySyncable* registry) { | 28 void RegisterPrefs(user_prefs::PrefRegistrySyncable* registry) { |
| 168 registry->RegisterInt64Pref( | |
| 169 prefs::kPasswordBubbleTimeStamp, | |
| 170 0, | |
| 171 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); | |
| 172 registry->RegisterIntegerPref( | 29 registry->RegisterIntegerPref( |
| 173 prefs::kPasswordBubbleNopesCount, | 30 prefs::kPasswordBubbleNopesCount, |
| 174 0, | 31 0, |
| 175 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); | 32 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); |
| 176 registry->RegisterListPref( | |
| 177 prefs::kPasswordBubbleLastInteractions, | |
| 178 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); | |
| 179 } | 33 } |
| 180 | 34 |
| 181 bool ShouldShowBubble(PrefService* prefs) { | 35 bool ShouldShowNeverForThisSiteDefault(PrefService* prefs) { |
| 182 if (!base::FieldTrialList::TrialExists(kExperimentName)) | 36 if (!base::FieldTrialList::TrialExists(kExperimentName)) |
| 183 return true; | 37 return false; |
| 184 std::string group_name = | 38 std::string param = |
| 185 base::FieldTrialList::FindFullName(kExperimentName); | 39 variations::GetVariationParamValue(kExperimentName, kParamNopeThreshold); |
| 186 | 40 |
| 187 if (group_name == kGroupTimeSpanBased) { | 41 int threshold = 0; |
| 188 return ShouldShowBubbleTimeSpanExperiment(prefs); | 42 return base::StringToInt(param, &threshold) && |
| 189 } | 43 GetCurrentNopesCount(prefs) >= threshold; |
| 190 if (group_name == kGroupProbabilityBased) { | |
| 191 return ShouldShowBubbleProbabilityExperiment(prefs); | |
| 192 } | |
| 193 | |
| 194 // The "Show Always" should be the default case. | |
| 195 return true; | |
| 196 } | 44 } |
| 197 | 45 |
| 198 void RecordBubbleClosed( | 46 void RecordBubbleClosed( |
| 199 PrefService* prefs, | 47 PrefService* prefs, |
| 200 password_manager::metrics_util::UIDismissalReason reason) { | 48 password_manager::metrics_util::UIDismissalReason reason) { |
| 201 UpdateTimeSpanPrefs(prefs, reason); | 49 // Clicking "Never" doesn't change the experiment state. |
| 202 UpdateProbabilityPrefs(prefs, reason); | 50 if (reason == password_manager::metrics_util::CLICKED_NEVER) |
| 51 return; |
| 52 int nopes_count = GetCurrentNopesCount(prefs); |
| 53 if (reason == password_manager::metrics_util::CLICKED_NOPE) |
| 54 nopes_count++; |
| 55 else |
| 56 nopes_count = 0; |
| 57 prefs->SetInteger(prefs::kPasswordBubbleNopesCount, nopes_count); |
| 203 } | 58 } |
| 204 | 59 |
| 205 } // namespace password_bubble_experiment | 60 } // namespace password_bubble_experiment |
| OLD | NEW |