• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "chrome/browser/omnibox/omnibox_field_trial.h"
6 
7 #include <string>
8 
9 #include "base/metrics/field_trial.h"
10 #include "base/strings/string_number_conversions.h"
11 #include "base/strings/string_split.h"
12 #include "base/strings/string_util.h"
13 #include "base/strings/stringprintf.h"
14 #include "chrome/browser/autocomplete/autocomplete_input.h"
15 #include "chrome/browser/search/search.h"
16 #include "chrome/common/metrics/variations/variation_ids.h"
17 #include "chrome/common/metrics/variations/variations_util.h"
18 #include "components/variations/metrics_util.h"
19 
20 namespace {
21 
22 // Field trial names.
23 const char kHUPCullRedirectsFieldTrialName[] = "OmniboxHUPCullRedirects";
24 const char kHUPCreateShorterMatchFieldTrialName[] =
25     "OmniboxHUPCreateShorterMatch";
26 const char kStopTimerFieldTrialName[] = "OmniboxStopTimer";
27 const char kEnableZeroSuggestGroupPrefix[] = "EnableZeroSuggest";
28 const char kEnableZeroSuggestMostVisitedGroupPrefix[] =
29     "EnableZeroSuggestMostVisited";
30 const char kEnableZeroSuggestAfterTypingGroupPrefix[] =
31     "EnableZeroSuggestAfterTyping";
32 
33 // The autocomplete dynamic field trial name prefix.  Each field trial is
34 // configured dynamically and is retrieved automatically by Chrome during
35 // the startup.
36 const char kAutocompleteDynamicFieldTrialPrefix[] = "AutocompleteDynamicTrial_";
37 // The maximum number of the autocomplete dynamic field trials (aka layers).
38 const int kMaxAutocompleteDynamicFieldTrials = 5;
39 
40 // Field trial experiment probabilities.
41 
42 // For HistoryURL provider cull redirects field trial, put 0% ( = 0/100 )
43 // of the users in the don't-cull-redirects experiment group.
44 // TODO(mpearson): Remove this field trial and the code it uses once I'm
45 // sure it's no longer needed.
46 const base::FieldTrial::Probability kHUPCullRedirectsFieldTrialDivisor = 100;
47 const base::FieldTrial::Probability
48     kHUPCullRedirectsFieldTrialExperimentFraction = 0;
49 
50 // For HistoryURL provider create shorter match field trial, put 0%
51 // ( = 25/100 ) of the users in the don't-create-a-shorter-match
52 // experiment group.
53 // TODO(mpearson): Remove this field trial and the code it uses once I'm
54 // sure it's no longer needed.
55 const base::FieldTrial::Probability
56     kHUPCreateShorterMatchFieldTrialDivisor = 100;
57 const base::FieldTrial::Probability
58     kHUPCreateShorterMatchFieldTrialExperimentFraction = 0;
59 
60 // Experiment group names.
61 
62 const char kStopTimerExperimentGroupName[] = "UseStopTimer";
63 
64 // Field trial IDs.
65 // Though they are not literally "const", they are set only once, in
66 // ActivateStaticTrials() below.
67 
68 // Whether the static field trials have been initialized by
69 // ActivateStaticTrials() method.
70 bool static_field_trials_initialized = false;
71 
72 // Field trial ID for the HistoryURL provider cull redirects experiment group.
73 int hup_dont_cull_redirects_experiment_group = 0;
74 
75 // Field trial ID for the HistoryURL provider create shorter match
76 // experiment group.
77 int hup_dont_create_shorter_match_experiment_group = 0;
78 
79 
80 // Concatenates the autocomplete dynamic field trial prefix with a field trial
81 // ID to form a complete autocomplete field trial name.
DynamicFieldTrialName(int id)82 std::string DynamicFieldTrialName(int id) {
83   return base::StringPrintf("%s%d", kAutocompleteDynamicFieldTrialPrefix, id);
84 }
85 
86 }  // namespace
87 
88 
ActivateStaticTrials()89 void OmniboxFieldTrial::ActivateStaticTrials() {
90   DCHECK(!static_field_trials_initialized);
91 
92   // Create the HistoryURL provider cull redirects field trial.
93   // Make it expire on March 1, 2013.
94   scoped_refptr<base::FieldTrial> trial(
95       base::FieldTrialList::FactoryGetFieldTrial(
96           kHUPCullRedirectsFieldTrialName, kHUPCullRedirectsFieldTrialDivisor,
97           "Standard", 2013, 3, 1, base::FieldTrial::ONE_TIME_RANDOMIZED, NULL));
98   hup_dont_cull_redirects_experiment_group =
99       trial->AppendGroup("DontCullRedirects",
100                          kHUPCullRedirectsFieldTrialExperimentFraction);
101 
102   // Create the HistoryURL provider create shorter match field trial.
103   // Make it expire on March 1, 2013.
104   trial = base::FieldTrialList::FactoryGetFieldTrial(
105       kHUPCreateShorterMatchFieldTrialName,
106       kHUPCreateShorterMatchFieldTrialDivisor, "Standard", 2013, 3, 1,
107       base::FieldTrial::ONE_TIME_RANDOMIZED, NULL);
108   hup_dont_create_shorter_match_experiment_group =
109       trial->AppendGroup("DontCreateShorterMatch",
110                          kHUPCreateShorterMatchFieldTrialExperimentFraction);
111 
112   static_field_trials_initialized = true;
113 }
114 
ActivateDynamicTrials()115 void OmniboxFieldTrial::ActivateDynamicTrials() {
116   // Initialize all autocomplete dynamic field trials.  This method may be
117   // called multiple times.
118   for (int i = 0; i < kMaxAutocompleteDynamicFieldTrials; ++i)
119     base::FieldTrialList::FindValue(DynamicFieldTrialName(i));
120 }
121 
GetDisabledProviderTypes()122 int OmniboxFieldTrial::GetDisabledProviderTypes() {
123   // Make sure that Autocomplete dynamic field trials are activated.  It's OK to
124   // call this method multiple times.
125   ActivateDynamicTrials();
126 
127   // Look for group names in form of "DisabledProviders_<mask>" where "mask"
128   // is a bitmap of disabled provider types (AutocompleteProvider::Type).
129   int provider_types = 0;
130   for (int i = 0; i < kMaxAutocompleteDynamicFieldTrials; ++i) {
131     std::string group_name = base::FieldTrialList::FindFullName(
132         DynamicFieldTrialName(i));
133     const char kDisabledProviders[] = "DisabledProviders_";
134     if (!StartsWithASCII(group_name, kDisabledProviders, true))
135       continue;
136     int types = 0;
137     if (!base::StringToInt(base::StringPiece(
138             group_name.substr(strlen(kDisabledProviders))), &types))
139       continue;
140     provider_types |= types;
141   }
142   return provider_types;
143 }
144 
GetActiveSuggestFieldTrialHashes(std::vector<uint32> * field_trial_hashes)145 void OmniboxFieldTrial::GetActiveSuggestFieldTrialHashes(
146     std::vector<uint32>* field_trial_hashes) {
147   field_trial_hashes->clear();
148   for (int i = 0; i < kMaxAutocompleteDynamicFieldTrials; ++i) {
149     const std::string& trial_name = DynamicFieldTrialName(i);
150     if (base::FieldTrialList::TrialExists(trial_name))
151       field_trial_hashes->push_back(metrics::HashName(trial_name));
152   }
153 }
154 
InHUPCullRedirectsFieldTrial()155 bool OmniboxFieldTrial::InHUPCullRedirectsFieldTrial() {
156   return base::FieldTrialList::TrialExists(kHUPCullRedirectsFieldTrialName);
157 }
158 
InHUPCullRedirectsFieldTrialExperimentGroup()159 bool OmniboxFieldTrial::InHUPCullRedirectsFieldTrialExperimentGroup() {
160   if (!base::FieldTrialList::TrialExists(kHUPCullRedirectsFieldTrialName))
161     return false;
162 
163   // Return true if we're in the experiment group.
164   const int group = base::FieldTrialList::FindValue(
165       kHUPCullRedirectsFieldTrialName);
166   return group == hup_dont_cull_redirects_experiment_group;
167 }
168 
InHUPCreateShorterMatchFieldTrial()169 bool OmniboxFieldTrial::InHUPCreateShorterMatchFieldTrial() {
170   return
171       base::FieldTrialList::TrialExists(kHUPCreateShorterMatchFieldTrialName);
172 }
173 
InHUPCreateShorterMatchFieldTrialExperimentGroup()174 bool OmniboxFieldTrial::InHUPCreateShorterMatchFieldTrialExperimentGroup() {
175   if (!base::FieldTrialList::TrialExists(kHUPCreateShorterMatchFieldTrialName))
176     return false;
177 
178   // Return true if we're in the experiment group.
179   const int group = base::FieldTrialList::FindValue(
180       kHUPCreateShorterMatchFieldTrialName);
181   return group == hup_dont_create_shorter_match_experiment_group;
182 }
183 
InStopTimerFieldTrialExperimentGroup()184 bool OmniboxFieldTrial::InStopTimerFieldTrialExperimentGroup() {
185   return (base::FieldTrialList::FindFullName(kStopTimerFieldTrialName) ==
186           kStopTimerExperimentGroupName);
187 }
188 
HasDynamicFieldTrialGroupPrefix(const char * group_prefix)189 bool OmniboxFieldTrial::HasDynamicFieldTrialGroupPrefix(
190     const char* group_prefix) {
191   // Make sure that Autocomplete dynamic field trials are activated.  It's OK to
192   // call this method multiple times.
193   ActivateDynamicTrials();
194 
195   // Look for group names starting with |group_prefix|.
196   for (int i = 0; i < kMaxAutocompleteDynamicFieldTrials; ++i) {
197     const std::string& group_name = base::FieldTrialList::FindFullName(
198         DynamicFieldTrialName(i));
199     if (StartsWithASCII(group_name, group_prefix, true))
200       return true;
201   }
202   return false;
203 }
204 
InZeroSuggestFieldTrial()205 bool OmniboxFieldTrial::InZeroSuggestFieldTrial() {
206   return HasDynamicFieldTrialGroupPrefix(kEnableZeroSuggestGroupPrefix);
207 }
208 
InZeroSuggestMostVisitedFieldTrial()209 bool OmniboxFieldTrial::InZeroSuggestMostVisitedFieldTrial() {
210   return HasDynamicFieldTrialGroupPrefix(
211       kEnableZeroSuggestMostVisitedGroupPrefix);
212 }
213 
InZeroSuggestAfterTypingFieldTrial()214 bool OmniboxFieldTrial::InZeroSuggestAfterTypingFieldTrial() {
215   return HasDynamicFieldTrialGroupPrefix(
216       kEnableZeroSuggestAfterTypingGroupPrefix);
217 }
218 
ShortcutsScoringMaxRelevance(AutocompleteInput::PageClassification current_page_classification,int * max_relevance)219 bool OmniboxFieldTrial::ShortcutsScoringMaxRelevance(
220     AutocompleteInput::PageClassification current_page_classification,
221     int* max_relevance) {
222   // The value of the rule is a string that encodes an integer containing
223   // the max relevance.
224   const std::string& max_relevance_str =
225       OmniboxFieldTrial::GetValueForRuleInContext(
226           kShortcutsScoringMaxRelevanceRule, current_page_classification);
227   if (max_relevance_str.empty())
228     return false;
229   if (!base::StringToInt(max_relevance_str, max_relevance))
230     return false;
231   return true;
232 }
233 
SearchHistoryPreventInlining(AutocompleteInput::PageClassification current_page_classification)234 bool OmniboxFieldTrial::SearchHistoryPreventInlining(
235     AutocompleteInput::PageClassification current_page_classification) {
236   return OmniboxFieldTrial::GetValueForRuleInContext(
237       kSearchHistoryRule, current_page_classification) == "PreventInlining";
238 }
239 
SearchHistoryDisable(AutocompleteInput::PageClassification current_page_classification)240 bool OmniboxFieldTrial::SearchHistoryDisable(
241     AutocompleteInput::PageClassification current_page_classification) {
242   return OmniboxFieldTrial::GetValueForRuleInContext(
243       kSearchHistoryRule, current_page_classification) == "Disable";
244 }
245 
GetDemotionsByType(AutocompleteInput::PageClassification current_page_classification,DemotionMultipliers * demotions_by_type)246 void OmniboxFieldTrial::GetDemotionsByType(
247     AutocompleteInput::PageClassification current_page_classification,
248     DemotionMultipliers* demotions_by_type) {
249   demotions_by_type->clear();
250   const std::string demotion_rule =
251       OmniboxFieldTrial::GetValueForRuleInContext(
252           kDemoteByTypeRule,
253           current_page_classification);
254   // The value of the DemoteByType rule is a comma-separated list of
255   // {ResultType + ":" + Number} where ResultType is an AutocompleteMatchType::
256   // Type enum represented as an integer and Number is an integer number
257   // between 0 and 100 inclusive.   Relevance scores of matches of that result
258   // type are multiplied by Number / 100.  100 means no change.
259   base::StringPairs kv_pairs;
260   if (base::SplitStringIntoKeyValuePairs(demotion_rule, ':', ',', &kv_pairs)) {
261     for (base::StringPairs::const_iterator it = kv_pairs.begin();
262          it != kv_pairs.end(); ++it) {
263       // This is a best-effort conversion; we trust the hand-crafted parameters
264       // downloaded from the server to be perfect.  There's no need to handle
265       // errors smartly.
266       int k, v;
267       base::StringToInt(it->first, &k);
268       base::StringToInt(it->second, &v);
269       (*demotions_by_type)[static_cast<AutocompleteMatchType::Type>(k)] =
270           static_cast<float>(v) / 100.0f;
271     }
272   }
273 }
274 
275 OmniboxFieldTrial::UndemotableTopMatchTypes
GetUndemotableTopTypes(AutocompleteInput::PageClassification current_page_classification)276 OmniboxFieldTrial::GetUndemotableTopTypes(
277     AutocompleteInput::PageClassification current_page_classification) {
278   UndemotableTopMatchTypes undemotable_types;
279   const std::string types_rule =
280       OmniboxFieldTrial::GetValueForRuleInContext(
281           kUndemotableTopTypeRule,
282           current_page_classification);
283   // The value of the UndemotableTopTypes rule is a comma-separated list of
284   // AutocompleteMatchType::Type enums represented as an integer. The
285   // DemoteByType rule does not apply to the top match if the type of the top
286   // match is in this list.
287   std::vector<std::string> types;
288   base::SplitString(types_rule, ',', &types);
289   for (std::vector<std::string>::const_iterator it = types.begin();
290        it != types.end(); ++it) {
291     // This is a best-effort conversion; we trust the hand-crafted parameters
292     // downloaded from the server to be perfect.  There's no need to handle
293     // errors smartly.
294     int t;
295     base::StringToInt(*it, &t);
296     undemotable_types.insert(static_cast<AutocompleteMatchType::Type>(t));
297   }
298   return undemotable_types;
299 }
300 
ReorderForLegalDefaultMatch(AutocompleteInput::PageClassification current_page_classification)301 bool OmniboxFieldTrial::ReorderForLegalDefaultMatch(
302     AutocompleteInput::PageClassification current_page_classification) {
303   return OmniboxFieldTrial::GetValueForRuleInContext(
304       kReorderForLegalDefaultMatchRule, current_page_classification) !=
305       kReorderForLegalDefaultMatchRuleDisabled;
306 }
307 
HQPBookmarkValue()308 int OmniboxFieldTrial::HQPBookmarkValue() {
309   std::string bookmark_value_str = chrome_variations::
310       GetVariationParamValue(kBundledExperimentFieldTrialName,
311                              kHQPBookmarkValueRule);
312   if (bookmark_value_str.empty())
313     return 1;
314   // This is a best-effort conversion; we trust the hand-crafted parameters
315   // downloaded from the server to be perfect.  There's no need for handle
316   // errors smartly.
317   int bookmark_value;
318   base::StringToInt(bookmark_value_str, &bookmark_value);
319   return bookmark_value;
320 }
321 
HQPDiscountFrecencyWhenFewVisits()322 bool OmniboxFieldTrial::HQPDiscountFrecencyWhenFewVisits() {
323   std::string discount_frecency_str = chrome_variations::
324       GetVariationParamValue(kBundledExperimentFieldTrialName,
325                              kHQPDiscountFrecencyWhenFewVisitsRule);
326   return discount_frecency_str == "true";
327 }
328 
HQPAllowMatchInTLDValue()329 bool OmniboxFieldTrial::HQPAllowMatchInTLDValue() {
330   std::string allow_match_in_tld_str = chrome_variations::
331       GetVariationParamValue(kBundledExperimentFieldTrialName,
332                              kHQPAllowMatchInTLDRule);
333   return allow_match_in_tld_str == "true";
334 }
335 
HQPAllowMatchInSchemeValue()336 bool OmniboxFieldTrial::HQPAllowMatchInSchemeValue() {
337   std::string allow_match_in_scheme_str = chrome_variations::
338       GetVariationParamValue(kBundledExperimentFieldTrialName,
339                              kHQPAllowMatchInSchemeRule);
340   return allow_match_in_scheme_str == "true";
341 }
342 
343 const char OmniboxFieldTrial::kBundledExperimentFieldTrialName[] =
344     "OmniboxBundledExperimentV1";
345 const char OmniboxFieldTrial::kShortcutsScoringMaxRelevanceRule[] =
346     "ShortcutsScoringMaxRelevance";
347 const char OmniboxFieldTrial::kSearchHistoryRule[] = "SearchHistory";
348 const char OmniboxFieldTrial::kDemoteByTypeRule[] = "DemoteByType";
349 const char OmniboxFieldTrial::kUndemotableTopTypeRule[] = "UndemotableTopTypes";
350 const char OmniboxFieldTrial::kReorderForLegalDefaultMatchRule[] =
351     "ReorderForLegalDefaultMatch";
352 const char OmniboxFieldTrial::kHQPBookmarkValueRule[] =
353     "HQPBookmarkValue";
354 const char OmniboxFieldTrial::kHQPDiscountFrecencyWhenFewVisitsRule[] =
355     "HQPDiscountFrecencyWhenFewVisits";
356 const char OmniboxFieldTrial::kHQPAllowMatchInTLDRule[] = "HQPAllowMatchInTLD";
357 const char OmniboxFieldTrial::kHQPAllowMatchInSchemeRule[] =
358     "HQPAllowMatchInScheme";
359 const char OmniboxFieldTrial::kReorderForLegalDefaultMatchRuleDisabled[] =
360     "DontReorderForLegalDefaultMatch";
361 
362 // Background and implementation details:
363 //
364 // Each experiment group in any field trial can come with an optional set of
365 // parameters (key-value pairs).  In the bundled omnibox experiment
366 // (kBundledExperimentFieldTrialName), each experiment group comes with a
367 // list of parameters in the form:
368 //   key=<Rule>:
369 //       <AutocompleteInput::PageClassification (as an int)>:
370 //       <whether Instant Extended is enabled (as a 1 or 0)>
371 //     (note that there are no linebreaks in keys; this format is for
372 //      presentation only>
373 //   value=<arbitrary string>
374 // Both the AutocompleteInput::PageClassification and the Instant Extended
375 // entries can be "*", which means this rule applies for all values of the
376 // matching portion of the context.
377 // One example parameter is
378 //   key=SearchHistory:6:1
379 //   value=PreventInlining
380 // This means in page classification context 6 (a search result page doing
381 // search term replacement) with Instant Extended enabled, the SearchHistory
382 // experiment should PreventInlining.
383 //
384 // When an exact match to the rule in the current context is missing, we
385 // give preference to a wildcard rule that matches the instant extended
386 // context over a wildcard rule that matches the page classification
387 // context.  Hopefully, though, users will write their field trial configs
388 // so as not to rely on this fall back order.
389 //
390 // In short, this function tries to find the value associated with key
391 // |rule|:|page_classification|:|instant_extended|, failing that it looks up
392 // |rule|:*:|instant_extended|, failing that it looks up
393 // |rule|:|page_classification|:*, failing that it looks up |rule|:*:*,
394 // and failing that it returns the empty string.
GetValueForRuleInContext(const std::string & rule,AutocompleteInput::PageClassification page_classification)395 std::string OmniboxFieldTrial::GetValueForRuleInContext(
396     const std::string& rule,
397     AutocompleteInput::PageClassification page_classification) {
398   std::map<std::string, std::string> params;
399   if (!chrome_variations::GetVariationParams(kBundledExperimentFieldTrialName,
400                                              &params)) {
401     return std::string();
402   }
403   const std::string page_classification_str =
404       base::IntToString(static_cast<int>(page_classification));
405   const std::string instant_extended =
406       chrome::IsInstantExtendedAPIEnabled() ? "1" : "0";
407   // Look up rule in this exact context.
408   std::map<std::string, std::string>::iterator it = params.find(
409       rule + ":" + page_classification_str + ":" + instant_extended);
410   if (it != params.end())
411     return it->second;
412   // Fall back to the global page classification context.
413   it = params.find(rule + ":*:" + instant_extended);
414   if (it != params.end())
415     return it->second;
416   // Fall back to the global instant extended context.
417   it = params.find(rule + ":" + page_classification_str + ":*");
418   if (it != params.end())
419     return it->second;
420   // Look up rule in the global context.
421   it = params.find(rule + ":*:*");
422   return (it != params.end()) ? it->second : std::string();
423 }
424