Home | History | Annotate | Download | only in omnibox
      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.
     93 std::string DynamicFieldTrialName(int id) {
     94   return base::StringPrintf("%s%d", kAutocompleteDynamicFieldTrialPrefix, id);
     95 }
     96 
     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 
    138 HUPScoringParams::ScoreBuckets::ScoreBuckets()
    139     : relevance_cap_(-1),
    140       half_life_days_(-1) {
    141 }
    142 
    143 HUPScoringParams::ScoreBuckets::~ScoreBuckets() {
    144 }
    145 
    146 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 
    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 
    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 
    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 
    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 
    228 bool OmniboxFieldTrial::InHUPCullRedirectsFieldTrial() {
    229   return base::FieldTrialList::TrialExists(kHUPCullRedirectsFieldTrialName);
    230 }
    231 
    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 
    242 bool OmniboxFieldTrial::InHUPCreateShorterMatchFieldTrial() {
    243   return
    244       base::FieldTrialList::TrialExists(kHUPCreateShorterMatchFieldTrialName);
    245 }
    246 
    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 
    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 
    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 
    282 bool OmniboxFieldTrial::InZeroSuggestFieldTrial() {
    283   return HasDynamicFieldTrialGroupPrefix(kEnableZeroSuggestGroupPrefix) ||
    284       chrome_variations::GetVariationParamValue(
    285           kBundledExperimentFieldTrialName, kZeroSuggestRule) == "true";
    286 }
    287 
    288 bool OmniboxFieldTrial::InZeroSuggestMostVisitedFieldTrial() {
    289   return HasDynamicFieldTrialGroupPrefix(
    290       kEnableZeroSuggestMostVisitedGroupPrefix) ||
    291       chrome_variations::GetVariationParamValue(
    292           kBundledExperimentFieldTrialName,
    293           kZeroSuggestVariantRule) == "MostVisited";
    294 }
    295 
    296 bool OmniboxFieldTrial::InZeroSuggestAfterTypingFieldTrial() {
    297   return HasDynamicFieldTrialGroupPrefix(
    298       kEnableZeroSuggestAfterTypingGroupPrefix) ||
    299       chrome_variations::GetVariationParamValue(
    300           kBundledExperimentFieldTrialName,
    301           kZeroSuggestVariantRule) == "AfterTyping";
    302 }
    303 
    304 bool OmniboxFieldTrial::InZeroSuggestPersonalizedFieldTrial() {
    305   return HasDynamicFieldTrialGroupPrefix(
    306       kEnableZeroSuggestPersonalizedGroupPrefix) ||
    307       chrome_variations::GetVariationParamValue(
    308           kBundledExperimentFieldTrialName,
    309           kZeroSuggestVariantRule) == "Personalized";
    310 }
    311 
    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 
    327 bool OmniboxFieldTrial::SearchHistoryPreventInlining(
    328     OmniboxEventProto::PageClassification current_page_classification) {
    329   return OmniboxFieldTrial::GetValueForRuleInContext(
    330       kSearchHistoryRule, current_page_classification) == "PreventInlining";
    331 }
    332 
    333 bool OmniboxFieldTrial::SearchHistoryDisable(
    334     OmniboxEventProto::PageClassification current_page_classification) {
    335   return OmniboxFieldTrial::GetValueForRuleInContext(
    336       kSearchHistoryRule, current_page_classification) == "Disable";
    337 }
    338 
    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 
    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 
    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 
    414 bool OmniboxFieldTrial::HQPAllowMatchInTLDValue() {
    415   return chrome_variations::GetVariationParamValue(
    416       kBundledExperimentFieldTrialName,
    417       kHQPAllowMatchInTLDRule) == "true";
    418 }
    419 
    420 bool OmniboxFieldTrial::HQPAllowMatchInSchemeValue() {
    421   return chrome_variations::GetVariationParamValue(
    422       kBundledExperimentFieldTrialName,
    423       kHQPAllowMatchInSchemeRule) == "true";
    424 }
    425 
    426 bool OmniboxFieldTrial::BookmarksIndexURLsValue() {
    427   return chrome_variations::GetVariationParamValue(
    428       kBundledExperimentFieldTrialName,
    429       kBookmarksIndexURLsRule) == "true";
    430 }
    431 
    432 bool OmniboxFieldTrial::DisableInlining() {
    433   return chrome_variations::GetVariationParamValue(
    434       kBundledExperimentFieldTrialName,
    435       kDisableInliningRule) == "true";
    436 }
    437 
    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 
    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.
    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