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 <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.
     82 std::string DynamicFieldTrialName(int id) {
     83   return base::StringPrintf("%s%d", kAutocompleteDynamicFieldTrialPrefix, id);
     84 }
     85 
     86 }  // namespace
     87 
     88 
     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 
    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 
    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 
    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 
    155 bool OmniboxFieldTrial::InHUPCullRedirectsFieldTrial() {
    156   return base::FieldTrialList::TrialExists(kHUPCullRedirectsFieldTrialName);
    157 }
    158 
    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 
    169 bool OmniboxFieldTrial::InHUPCreateShorterMatchFieldTrial() {
    170   return
    171       base::FieldTrialList::TrialExists(kHUPCreateShorterMatchFieldTrialName);
    172 }
    173 
    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 
    184 bool OmniboxFieldTrial::InStopTimerFieldTrialExperimentGroup() {
    185   return (base::FieldTrialList::FindFullName(kStopTimerFieldTrialName) ==
    186           kStopTimerExperimentGroupName);
    187 }
    188 
    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 
    205 bool OmniboxFieldTrial::InZeroSuggestFieldTrial() {
    206   return HasDynamicFieldTrialGroupPrefix(kEnableZeroSuggestGroupPrefix);
    207 }
    208 
    209 bool OmniboxFieldTrial::InZeroSuggestMostVisitedFieldTrial() {
    210   return HasDynamicFieldTrialGroupPrefix(
    211       kEnableZeroSuggestMostVisitedGroupPrefix);
    212 }
    213 
    214 bool OmniboxFieldTrial::InZeroSuggestAfterTypingFieldTrial() {
    215   return HasDynamicFieldTrialGroupPrefix(
    216       kEnableZeroSuggestAfterTypingGroupPrefix);
    217 }
    218 
    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 
    234 bool OmniboxFieldTrial::SearchHistoryPreventInlining(
    235     AutocompleteInput::PageClassification current_page_classification) {
    236   return OmniboxFieldTrial::GetValueForRuleInContext(
    237       kSearchHistoryRule, current_page_classification) == "PreventInlining";
    238 }
    239 
    240 bool OmniboxFieldTrial::SearchHistoryDisable(
    241     AutocompleteInput::PageClassification current_page_classification) {
    242   return OmniboxFieldTrial::GetValueForRuleInContext(
    243       kSearchHistoryRule, current_page_classification) == "Disable";
    244 }
    245 
    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
    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 
    301 bool OmniboxFieldTrial::ReorderForLegalDefaultMatch(
    302     AutocompleteInput::PageClassification current_page_classification) {
    303   return OmniboxFieldTrial::GetValueForRuleInContext(
    304       kReorderForLegalDefaultMatchRule, current_page_classification) !=
    305       kReorderForLegalDefaultMatchRuleDisabled;
    306 }
    307 
    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 
    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 
    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 
    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.
    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