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 int nopes_count = GetCurrentNopesCount(prefs); |
202 UpdateProbabilityPrefs(prefs, reason); | 50 if (reason == password_manager::metrics_util::CLICKED_NOPE) |
51 nopes_count++; | |
52 else | |
53 nopes_count = 0; | |
vabr (Chromium)
2015/03/24 09:09:04
I'm not sure why clicking "Never" or just abandoni
vasilii
2015/03/24 10:30:53
The case of NO_DIRECT_INTERACTION was discussed wi
vabr (Chromium)
2015/03/24 10:40:59
Fair enough for NO_DIRECT_INTERACTION. To be clear
vasilii
2015/03/24 11:00:04
I changed the algorithm and the test.
| |
54 prefs->SetInteger(prefs::kPasswordBubbleNopesCount, nopes_count); | |
203 } | 55 } |
204 | 56 |
205 } // namespace password_bubble_experiment | 57 } // namespace password_bubble_experiment |
OLD | NEW |