• 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 <cmath>
8 #include <string>
9 
10 #include "base/command_line.h"
11 #include "base/metrics/field_trial.h"
12 #include "base/strings/string_number_conversions.h"
13 #include "base/strings/string_split.h"
14 #include "base/strings/string_util.h"
15 #include "base/strings/stringprintf.h"
16 #include "base/time/time.h"
17 #include "chrome/browser/search/search.h"
18 #include "chrome/common/chrome_switches.h"
19 #include "chrome/common/variations/variation_ids.h"
20 #include "components/metrics/proto/omnibox_event.pb.h"
21 #include "components/variations/active_field_trials.h"
22 #include "components/variations/metrics_util.h"
23 #include "components/variations/variations_associated_data.h"
24 
25 using metrics::OmniboxEventProto;
26 
27 namespace {
28 
29 typedef std::map<std::string, std::string> VariationParams;
30 typedef HUPScoringParams::ScoreBuckets ScoreBuckets;
31 
32 // Field trial names.
33 const char kHUPCullRedirectsFieldTrialName[] = "OmniboxHUPCullRedirects";
34 const char kHUPCreateShorterMatchFieldTrialName[] =
35     "OmniboxHUPCreateShorterMatch";
36 const char kStopTimerFieldTrialName[] = "OmniboxStopTimer";
37 
38 // In dynamic field trials, we use these group names to switch between
39 // different zero suggest implementations.
40 const char kEnableZeroSuggestGroupPrefix[] = "EnableZeroSuggest";
41 const char kEnableZeroSuggestMostVisitedGroupPrefix[] =
42     "EnableZeroSuggestMostVisited";
43 const char kEnableZeroSuggestAfterTypingGroupPrefix[] =
44     "EnableZeroSuggestAfterTyping";
45 const char kEnableZeroSuggestPersonalizedGroupPrefix[] =
46     "EnableZeroSuggestPersonalized";
47 
48 // The autocomplete dynamic field trial name prefix.  Each field trial is
49 // configured dynamically and is retrieved automatically by Chrome during
50 // the startup.
51 const char kAutocompleteDynamicFieldTrialPrefix[] = "AutocompleteDynamicTrial_";
52 // The maximum number of the autocomplete dynamic field trials (aka layers).
53 const int kMaxAutocompleteDynamicFieldTrials = 5;
54 
55 // Field trial experiment probabilities.
56 
57 // For HistoryURL provider cull redirects field trial, put 0% ( = 0/100 )
58 // of the users in the don't-cull-redirects experiment group.
59 // TODO(mpearson): Remove this field trial and the code it uses once I'm
60 // sure it's no longer needed.
61 const base::FieldTrial::Probability kHUPCullRedirectsFieldTrialDivisor = 100;
62 const base::FieldTrial::Probability
63     kHUPCullRedirectsFieldTrialExperimentFraction = 0;
64 
65 // For HistoryURL provider create shorter match field trial, put 0%
66 // ( = 25/100 ) of the users in the don't-create-a-shorter-match
67 // experiment group.
68 // TODO(mpearson): Remove this field trial and the code it uses once I'm
69 // sure it's no longer needed.
70 const base::FieldTrial::Probability
71     kHUPCreateShorterMatchFieldTrialDivisor = 100;
72 const base::FieldTrial::Probability
73     kHUPCreateShorterMatchFieldTrialExperimentFraction = 0;
74 
75 // Field trial IDs.
76 // Though they are not literally "const", they are set only once, in
77 // ActivateStaticTrials() below.
78 
79 // Whether the static field trials have been initialized by
80 // ActivateStaticTrials() method.
81 bool static_field_trials_initialized = false;
82 
83 // Field trial ID for the HistoryURL provider cull redirects experiment group.
84 int hup_dont_cull_redirects_experiment_group = 0;
85 
86 // Field trial ID for the HistoryURL provider create shorter match
87 // experiment group.
88 int hup_dont_create_shorter_match_experiment_group = 0;
89 
90 
91 // Concatenates the autocomplete dynamic field trial prefix with a field trial
92 // ID to form a complete autocomplete field trial name.
DynamicFieldTrialName(int id)93 std::string DynamicFieldTrialName(int id) {
94   return base::StringPrintf("%s%d", kAutocompleteDynamicFieldTrialPrefix, id);
95 }
96 
InitializeScoreBuckets(const VariationParams & params,const char * relevance_cap_param,const char * half_life_param,const char * score_buckets_param,ScoreBuckets * score_buckets)97 void InitializeScoreBuckets(const VariationParams& params,
98                             const char* relevance_cap_param,
99                             const char* half_life_param,
100                             const char* score_buckets_param,
101                             ScoreBuckets* score_buckets) {
102   VariationParams::const_iterator it = params.find(relevance_cap_param);
103   if (it != params.end()) {
104     int relevance_cap;
105     if (base::StringToInt(it->second, &relevance_cap))
106       score_buckets->set_relevance_cap(relevance_cap);
107   }
108 
109   it = params.find(half_life_param);
110   if (it != params.end()) {
111     int half_life_days;
112     if (base::StringToInt(it->second, &half_life_days))
113       score_buckets->set_half_life_days(half_life_days);
114   }
115 
116   it = params.find(score_buckets_param);
117   if (it != params.end()) {
118     // The value of the score bucket is a comma-separated list of
119     // {DecayedCount + ":" + MaxRelevance}.
120     base::StringPairs kv_pairs;
121     if (base::SplitStringIntoKeyValuePairs(it->second, ':', ',', &kv_pairs)) {
122       for (base::StringPairs::const_iterator it = kv_pairs.begin();
123            it != kv_pairs.end(); ++it) {
124         ScoreBuckets::CountMaxRelevance bucket;
125         base::StringToDouble(it->first, &bucket.first);
126         base::StringToInt(it->second, &bucket.second);
127         score_buckets->buckets().push_back(bucket);
128       }
129       std::sort(score_buckets->buckets().begin(),
130                 score_buckets->buckets().end(),
131                 std::greater<ScoreBuckets::CountMaxRelevance>());
132     }
133   }
134 }
135 
136 }  // namespace
137 
ScoreBuckets()138 HUPScoringParams::ScoreBuckets::ScoreBuckets()
139     : relevance_cap_(-1),
140       half_life_days_(-1) {
141 }
142 
~ScoreBuckets()143 HUPScoringParams::ScoreBuckets::~ScoreBuckets() {
144 }
145 
HalfLifeTimeDecay(const base::TimeDelta & elapsed_time) const146 double HUPScoringParams::ScoreBuckets::HalfLifeTimeDecay(
147     const base::TimeDelta& elapsed_time) const {
148   double time_ms;
149   if ((half_life_days_ <= 0) ||
150       ((time_ms = elapsed_time.InMillisecondsF()) <= 0))
151     return 1.0;
152 
153   const double half_life_intervals =
154       time_ms / base::TimeDelta::FromDays(half_life_days_).InMillisecondsF();
155   return pow(2.0, -half_life_intervals);
156 }
157 
ActivateStaticTrials()158 void OmniboxFieldTrial::ActivateStaticTrials() {
159   DCHECK(!static_field_trials_initialized);
160 
161   // Create the HistoryURL provider cull redirects field trial.
162   // Make it expire on March 1, 2013.
163   scoped_refptr<base::FieldTrial> trial(
164       base::FieldTrialList::FactoryGetFieldTrial(
165           kHUPCullRedirectsFieldTrialName, kHUPCullRedirectsFieldTrialDivisor,
166           "Standard", 2013, 3, 1, base::FieldTrial::ONE_TIME_RANDOMIZED, NULL));
167   hup_dont_cull_redirects_experiment_group =
168       trial->AppendGroup("DontCullRedirects",
169                          kHUPCullRedirectsFieldTrialExperimentFraction);
170 
171   // Create the HistoryURL provider create shorter match field trial.
172   // Make it expire on March 1, 2013.
173   trial = base::FieldTrialList::FactoryGetFieldTrial(
174       kHUPCreateShorterMatchFieldTrialName,
175       kHUPCreateShorterMatchFieldTrialDivisor, "Standard", 2013, 3, 1,
176       base::FieldTrial::ONE_TIME_RANDOMIZED, NULL);
177   hup_dont_create_shorter_match_experiment_group =
178       trial->AppendGroup("DontCreateShorterMatch",
179                          kHUPCreateShorterMatchFieldTrialExperimentFraction);
180 
181   static_field_trials_initialized = true;
182 }
183 
ActivateDynamicTrials()184 void OmniboxFieldTrial::ActivateDynamicTrials() {
185   // Initialize all autocomplete dynamic field trials.  This method may be
186   // called multiple times.
187   for (int i = 0; i < kMaxAutocompleteDynamicFieldTrials; ++i)
188     base::FieldTrialList::FindValue(DynamicFieldTrialName(i));
189 }
190 
GetDisabledProviderTypes()191 int OmniboxFieldTrial::GetDisabledProviderTypes() {
192   // Make sure that Autocomplete dynamic field trials are activated.  It's OK to
193   // call this method multiple times.
194   ActivateDynamicTrials();
195 
196   // Look for group names in form of "DisabledProviders_<mask>" where "mask"
197   // is a bitmap of disabled provider types (AutocompleteProvider::Type).
198   int provider_types = 0;
199   for (int i = 0; i < kMaxAutocompleteDynamicFieldTrials; ++i) {
200     std::string group_name = base::FieldTrialList::FindFullName(
201         DynamicFieldTrialName(i));
202     const char kDisabledProviders[] = "DisabledProviders_";
203     if (!StartsWithASCII(group_name, kDisabledProviders, true))
204       continue;
205     int types = 0;
206     if (!base::StringToInt(base::StringPiece(
207             group_name.substr(strlen(kDisabledProviders))), &types))
208       continue;
209     provider_types |= types;
210   }
211   return provider_types;
212 }
213 
GetActiveSuggestFieldTrialHashes(std::vector<uint32> * field_trial_hashes)214 void OmniboxFieldTrial::GetActiveSuggestFieldTrialHashes(
215     std::vector<uint32>* field_trial_hashes) {
216   field_trial_hashes->clear();
217   for (int i = 0; i < kMaxAutocompleteDynamicFieldTrials; ++i) {
218     const std::string& trial_name = DynamicFieldTrialName(i);
219     if (base::FieldTrialList::TrialExists(trial_name))
220       field_trial_hashes->push_back(metrics::HashName(trial_name));
221   }
222   if (base::FieldTrialList::TrialExists(kBundledExperimentFieldTrialName)) {
223     field_trial_hashes->push_back(
224         metrics::HashName(kBundledExperimentFieldTrialName));
225   }
226 }
227 
InHUPCullRedirectsFieldTrial()228 bool OmniboxFieldTrial::InHUPCullRedirectsFieldTrial() {
229   return base::FieldTrialList::TrialExists(kHUPCullRedirectsFieldTrialName);
230 }
231 
InHUPCullRedirectsFieldTrialExperimentGroup()232 bool OmniboxFieldTrial::InHUPCullRedirectsFieldTrialExperimentGroup() {
233   if (!base::FieldTrialList::TrialExists(kHUPCullRedirectsFieldTrialName))
234     return false;
235 
236   // Return true if we're in the experiment group.
237   const int group = base::FieldTrialList::FindValue(
238       kHUPCullRedirectsFieldTrialName);
239   return group == hup_dont_cull_redirects_experiment_group;
240 }
241 
InHUPCreateShorterMatchFieldTrial()242 bool OmniboxFieldTrial::InHUPCreateShorterMatchFieldTrial() {
243   return
244       base::FieldTrialList::TrialExists(kHUPCreateShorterMatchFieldTrialName);
245 }
246 
InHUPCreateShorterMatchFieldTrialExperimentGroup()247 bool OmniboxFieldTrial::InHUPCreateShorterMatchFieldTrialExperimentGroup() {
248   if (!base::FieldTrialList::TrialExists(kHUPCreateShorterMatchFieldTrialName))
249     return false;
250 
251   // Return true if we're in the experiment group.
252   const int group = base::FieldTrialList::FindValue(
253       kHUPCreateShorterMatchFieldTrialName);
254   return group == hup_dont_create_shorter_match_experiment_group;
255 }
256 
StopTimerFieldTrialDuration()257 base::TimeDelta OmniboxFieldTrial::StopTimerFieldTrialDuration() {
258   int stop_timer_ms;
259   if (base::StringToInt(
260       base::FieldTrialList::FindFullName(kStopTimerFieldTrialName),
261           &stop_timer_ms))
262     return base::TimeDelta::FromMilliseconds(stop_timer_ms);
263   return base::TimeDelta::FromMilliseconds(1500);
264 }
265 
HasDynamicFieldTrialGroupPrefix(const char * group_prefix)266 bool OmniboxFieldTrial::HasDynamicFieldTrialGroupPrefix(
267     const char* group_prefix) {
268   // Make sure that Autocomplete dynamic field trials are activated.  It's OK to
269   // call this method multiple times.
270   ActivateDynamicTrials();
271 
272   // Look for group names starting with |group_prefix|.
273   for (int i = 0; i < kMaxAutocompleteDynamicFieldTrials; ++i) {
274     const std::string& group_name = base::FieldTrialList::FindFullName(
275         DynamicFieldTrialName(i));
276     if (StartsWithASCII(group_name, group_prefix, true))
277       return true;
278   }
279   return false;
280 }
281 
InZeroSuggestFieldTrial()282 bool OmniboxFieldTrial::InZeroSuggestFieldTrial() {
283   return HasDynamicFieldTrialGroupPrefix(kEnableZeroSuggestGroupPrefix) ||
284       chrome_variations::GetVariationParamValue(
285           kBundledExperimentFieldTrialName, kZeroSuggestRule) == "true";
286 }
287 
InZeroSuggestMostVisitedFieldTrial()288 bool OmniboxFieldTrial::InZeroSuggestMostVisitedFieldTrial() {
289   return HasDynamicFieldTrialGroupPrefix(
290       kEnableZeroSuggestMostVisitedGroupPrefix) ||
291       chrome_variations::GetVariationParamValue(
292           kBundledExperimentFieldTrialName,
293           kZeroSuggestVariantRule) == "MostVisited";
294 }
295 
InZeroSuggestAfterTypingFieldTrial()296 bool OmniboxFieldTrial::InZeroSuggestAfterTypingFieldTrial() {
297   return HasDynamicFieldTrialGroupPrefix(
298       kEnableZeroSuggestAfterTypingGroupPrefix) ||
299       chrome_variations::GetVariationParamValue(
300           kBundledExperimentFieldTrialName,
301           kZeroSuggestVariantRule) == "AfterTyping";
302 }
303 
InZeroSuggestPersonalizedFieldTrial()304 bool OmniboxFieldTrial::InZeroSuggestPersonalizedFieldTrial() {
305   return HasDynamicFieldTrialGroupPrefix(
306       kEnableZeroSuggestPersonalizedGroupPrefix) ||
307       chrome_variations::GetVariationParamValue(
308           kBundledExperimentFieldTrialName,
309           kZeroSuggestVariantRule) == "Personalized";
310 }
311 
ShortcutsScoringMaxRelevance(OmniboxEventProto::PageClassification current_page_classification,int * max_relevance)312 bool OmniboxFieldTrial::ShortcutsScoringMaxRelevance(
313     OmniboxEventProto::PageClassification current_page_classification,
314     int* max_relevance) {
315   // The value of the rule is a string that encodes an integer containing
316   // the max relevance.
317   const std::string& max_relevance_str =
318       OmniboxFieldTrial::GetValueForRuleInContext(
319           kShortcutsScoringMaxRelevanceRule, current_page_classification);
320   if (max_relevance_str.empty())
321     return false;
322   if (!base::StringToInt(max_relevance_str, max_relevance))
323     return false;
324   return true;
325 }
326 
SearchHistoryPreventInlining(OmniboxEventProto::PageClassification current_page_classification)327 bool OmniboxFieldTrial::SearchHistoryPreventInlining(
328     OmniboxEventProto::PageClassification current_page_classification) {
329   return OmniboxFieldTrial::GetValueForRuleInContext(
330       kSearchHistoryRule, current_page_classification) == "PreventInlining";
331 }
332 
SearchHistoryDisable(OmniboxEventProto::PageClassification current_page_classification)333 bool OmniboxFieldTrial::SearchHistoryDisable(
334     OmniboxEventProto::PageClassification current_page_classification) {
335   return OmniboxFieldTrial::GetValueForRuleInContext(
336       kSearchHistoryRule, current_page_classification) == "Disable";
337 }
338 
GetDemotionsByType(OmniboxEventProto::PageClassification current_page_classification,DemotionMultipliers * demotions_by_type)339 void OmniboxFieldTrial::GetDemotionsByType(
340     OmniboxEventProto::PageClassification current_page_classification,
341     DemotionMultipliers* demotions_by_type) {
342   demotions_by_type->clear();
343   std::string demotion_rule = OmniboxFieldTrial::GetValueForRuleInContext(
344       kDemoteByTypeRule, current_page_classification);
345   // If there is no demotion rule for this context, then use the default
346   // value for that context.  At the moment the default value is non-empty
347   // only for the fakebox-focus context.
348   if (demotion_rule.empty() &&
349       (current_page_classification ==
350        OmniboxEventProto::INSTANT_NTP_WITH_FAKEBOX_AS_STARTING_FOCUS))
351     demotion_rule = "1:61,2:61,3:61,4:61,12:61";
352 
353   // The value of the DemoteByType rule is a comma-separated list of
354   // {ResultType + ":" + Number} where ResultType is an AutocompleteMatchType::
355   // Type enum represented as an integer and Number is an integer number
356   // between 0 and 100 inclusive.   Relevance scores of matches of that result
357   // type are multiplied by Number / 100.  100 means no change.
358   base::StringPairs kv_pairs;
359   if (base::SplitStringIntoKeyValuePairs(demotion_rule, ':', ',', &kv_pairs)) {
360     for (base::StringPairs::const_iterator it = kv_pairs.begin();
361          it != kv_pairs.end(); ++it) {
362       // This is a best-effort conversion; we trust the hand-crafted parameters
363       // downloaded from the server to be perfect.  There's no need to handle
364       // errors smartly.
365       int k, v;
366       base::StringToInt(it->first, &k);
367       base::StringToInt(it->second, &v);
368       (*demotions_by_type)[static_cast<AutocompleteMatchType::Type>(k)] =
369           static_cast<float>(v) / 100.0f;
370     }
371   }
372 }
373 
GetExperimentalHUPScoringParams(HUPScoringParams * scoring_params)374 void OmniboxFieldTrial::GetExperimentalHUPScoringParams(
375     HUPScoringParams* scoring_params) {
376   scoring_params->experimental_scoring_enabled = false;
377 
378   VariationParams params;
379   if (!chrome_variations::GetVariationParams(kBundledExperimentFieldTrialName,
380                                              &params))
381     return;
382 
383   VariationParams::const_iterator it = params.find(kHUPNewScoringEnabledParam);
384   if (it != params.end()) {
385     int enabled = 0;
386     if (base::StringToInt(it->second, &enabled))
387       scoring_params->experimental_scoring_enabled = (enabled != 0);
388   }
389 
390   InitializeScoreBuckets(params, kHUPNewScoringTypedCountRelevanceCapParam,
391       kHUPNewScoringTypedCountHalfLifeTimeParam,
392       kHUPNewScoringTypedCountScoreBucketsParam,
393       &scoring_params->typed_count_buckets);
394   InitializeScoreBuckets(params, kHUPNewScoringVisitedCountRelevanceCapParam,
395       kHUPNewScoringVisitedCountHalfLifeTimeParam,
396       kHUPNewScoringVisitedCountScoreBucketsParam,
397       &scoring_params->visited_count_buckets);
398 }
399 
HQPBookmarkValue()400 int OmniboxFieldTrial::HQPBookmarkValue() {
401   std::string bookmark_value_str = chrome_variations::
402       GetVariationParamValue(kBundledExperimentFieldTrialName,
403                              kHQPBookmarkValueRule);
404   if (bookmark_value_str.empty())
405     return 10;
406   // This is a best-effort conversion; we trust the hand-crafted parameters
407   // downloaded from the server to be perfect.  There's no need for handle
408   // errors smartly.
409   int bookmark_value;
410   base::StringToInt(bookmark_value_str, &bookmark_value);
411   return bookmark_value;
412 }
413 
HQPAllowMatchInTLDValue()414 bool OmniboxFieldTrial::HQPAllowMatchInTLDValue() {
415   return chrome_variations::GetVariationParamValue(
416       kBundledExperimentFieldTrialName,
417       kHQPAllowMatchInTLDRule) == "true";
418 }
419 
HQPAllowMatchInSchemeValue()420 bool OmniboxFieldTrial::HQPAllowMatchInSchemeValue() {
421   return chrome_variations::GetVariationParamValue(
422       kBundledExperimentFieldTrialName,
423       kHQPAllowMatchInSchemeRule) == "true";
424 }
425 
BookmarksIndexURLsValue()426 bool OmniboxFieldTrial::BookmarksIndexURLsValue() {
427   return chrome_variations::GetVariationParamValue(
428       kBundledExperimentFieldTrialName,
429       kBookmarksIndexURLsRule) == "true";
430 }
431 
DisableInlining()432 bool OmniboxFieldTrial::DisableInlining() {
433   return chrome_variations::GetVariationParamValue(
434       kBundledExperimentFieldTrialName,
435       kDisableInliningRule) == "true";
436 }
437 
EnableAnswersInSuggest()438 bool OmniboxFieldTrial::EnableAnswersInSuggest() {
439   const CommandLine* cl = CommandLine::ForCurrentProcess();
440   if (cl->HasSwitch(switches::kDisableAnswersInSuggest))
441     return false;
442   if (cl->HasSwitch(switches::kEnableAnswersInSuggest))
443     return true;
444 
445   return chrome_variations::GetVariationParamValue(
446       kBundledExperimentFieldTrialName,
447       kAnswersInSuggestRule) == "true";
448 }
449 
AddUWYTMatchEvenIfPromotedURLs()450 bool OmniboxFieldTrial::AddUWYTMatchEvenIfPromotedURLs() {
451   return chrome_variations::GetVariationParamValue(
452       kBundledExperimentFieldTrialName,
453       kAddUWYTMatchEvenIfPromotedURLsRule) == "true";
454 }
455 
456 const char OmniboxFieldTrial::kBundledExperimentFieldTrialName[] =
457     "OmniboxBundledExperimentV1";
458 const char OmniboxFieldTrial::kShortcutsScoringMaxRelevanceRule[] =
459     "ShortcutsScoringMaxRelevance";
460 const char OmniboxFieldTrial::kSearchHistoryRule[] = "SearchHistory";
461 const char OmniboxFieldTrial::kDemoteByTypeRule[] = "DemoteByType";
462 const char OmniboxFieldTrial::kHQPBookmarkValueRule[] =
463     "HQPBookmarkValue";
464 const char OmniboxFieldTrial::kHQPAllowMatchInTLDRule[] = "HQPAllowMatchInTLD";
465 const char OmniboxFieldTrial::kHQPAllowMatchInSchemeRule[] =
466     "HQPAllowMatchInScheme";
467 const char OmniboxFieldTrial::kZeroSuggestRule[] = "ZeroSuggest";
468 const char OmniboxFieldTrial::kZeroSuggestVariantRule[] = "ZeroSuggestVariant";
469 const char OmniboxFieldTrial::kBookmarksIndexURLsRule[] = "BookmarksIndexURLs";
470 const char OmniboxFieldTrial::kDisableInliningRule[] = "DisableInlining";
471 const char OmniboxFieldTrial::kAnswersInSuggestRule[] = "AnswersInSuggest";
472 const char OmniboxFieldTrial::kAddUWYTMatchEvenIfPromotedURLsRule[] =
473     "AddUWYTMatchEvenIfPromotedURLs";
474 
475 const char OmniboxFieldTrial::kHUPNewScoringEnabledParam[] =
476     "HUPExperimentalScoringEnabled";
477 const char OmniboxFieldTrial::kHUPNewScoringTypedCountRelevanceCapParam[] =
478     "TypedCountRelevanceCap";
479 const char OmniboxFieldTrial::kHUPNewScoringTypedCountHalfLifeTimeParam[] =
480     "TypedCountHalfLifeTime";
481 const char OmniboxFieldTrial::kHUPNewScoringTypedCountScoreBucketsParam[] =
482     "TypedCountScoreBuckets";
483 const char OmniboxFieldTrial::kHUPNewScoringVisitedCountRelevanceCapParam[] =
484     "VisitedCountRelevanceCap";
485 const char OmniboxFieldTrial::kHUPNewScoringVisitedCountHalfLifeTimeParam[] =
486     "VisitedCountHalfLifeTime";
487 const char OmniboxFieldTrial::kHUPNewScoringVisitedCountScoreBucketsParam[] =
488     "VisitedCountScoreBuckets";
489 
490 // Background and implementation details:
491 //
492 // Each experiment group in any field trial can come with an optional set of
493 // parameters (key-value pairs).  In the bundled omnibox experiment
494 // (kBundledExperimentFieldTrialName), each experiment group comes with a
495 // list of parameters in the form:
496 //   key=<Rule>:
497 //       <OmniboxEventProto::PageClassification (as an int)>:
498 //       <whether Instant Extended is enabled (as a 1 or 0)>
499 //     (note that there are no linebreaks in keys; this format is for
500 //      presentation only>
501 //   value=<arbitrary string>
502 // Both the OmniboxEventProto::PageClassification and the Instant Extended
503 // entries can be "*", which means this rule applies for all values of the
504 // matching portion of the context.
505 // One example parameter is
506 //   key=SearchHistory:6:1
507 //   value=PreventInlining
508 // This means in page classification context 6 (a search result page doing
509 // search term replacement) with Instant Extended enabled, the SearchHistory
510 // experiment should PreventInlining.
511 //
512 // When an exact match to the rule in the current context is missing, we
513 // give preference to a wildcard rule that matches the instant extended
514 // context over a wildcard rule that matches the page classification
515 // context.  Hopefully, though, users will write their field trial configs
516 // so as not to rely on this fall back order.
517 //
518 // In short, this function tries to find the value associated with key
519 // |rule|:|page_classification|:|instant_extended|, failing that it looks up
520 // |rule|:*:|instant_extended|, failing that it looks up
521 // |rule|:|page_classification|:*, failing that it looks up |rule|:*:*,
522 // and failing that it returns the empty string.
GetValueForRuleInContext(const std::string & rule,OmniboxEventProto::PageClassification page_classification)523 std::string OmniboxFieldTrial::GetValueForRuleInContext(
524     const std::string& rule,
525     OmniboxEventProto::PageClassification page_classification) {
526   VariationParams params;
527   if (!chrome_variations::GetVariationParams(kBundledExperimentFieldTrialName,
528                                              &params)) {
529     return std::string();
530   }
531   const std::string page_classification_str =
532       base::IntToString(static_cast<int>(page_classification));
533   const std::string instant_extended =
534       chrome::IsInstantExtendedAPIEnabled() ? "1" : "0";
535   // Look up rule in this exact context.
536   VariationParams::const_iterator it = params.find(
537       rule + ":" + page_classification_str + ":" + instant_extended);
538   if (it != params.end())
539     return it->second;
540   // Fall back to the global page classification context.
541   it = params.find(rule + ":*:" + instant_extended);
542   if (it != params.end())
543     return it->second;
544   // Fall back to the global instant extended context.
545   it = params.find(rule + ":" + page_classification_str + ":*");
546   if (it != params.end())
547     return it->second;
548   // Look up rule in the global context.
549   it = params.find(rule + ":*:*");
550   return (it != params.end()) ? it->second : std::string();
551 }
552