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/metrics_util.h" 17 #include "chrome/common/metrics/variations/variation_ids.h" 18 #include "chrome/common/metrics/variations/variations_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 28 // The autocomplete dynamic field trial name prefix. Each field trial is 29 // configured dynamically and is retrieved automatically by Chrome during 30 // the startup. 31 const char kAutocompleteDynamicFieldTrialPrefix[] = "AutocompleteDynamicTrial_"; 32 // The maximum number of the autocomplete dynamic field trials (aka layers). 33 const int kMaxAutocompleteDynamicFieldTrials = 5; 34 35 // Field trial experiment probabilities. 36 37 // For HistoryURL provider cull redirects field trial, put 0% ( = 0/100 ) 38 // of the users in the don't-cull-redirects experiment group. 39 // TODO(mpearson): Remove this field trial and the code it uses once I'm 40 // sure it's no longer needed. 41 const base::FieldTrial::Probability kHUPCullRedirectsFieldTrialDivisor = 100; 42 const base::FieldTrial::Probability 43 kHUPCullRedirectsFieldTrialExperimentFraction = 0; 44 45 // For HistoryURL provider create shorter match field trial, put 0% 46 // ( = 25/100 ) of the users in the don't-create-a-shorter-match 47 // experiment group. 48 // TODO(mpearson): Remove this field trial and the code it uses once I'm 49 // sure it's no longer needed. 50 const base::FieldTrial::Probability 51 kHUPCreateShorterMatchFieldTrialDivisor = 100; 52 const base::FieldTrial::Probability 53 kHUPCreateShorterMatchFieldTrialExperimentFraction = 0; 54 55 // Experiment group names. 56 57 const char kStopTimerExperimentGroupName[] = "UseStopTimer"; 58 59 // Field trial IDs. 60 // Though they are not literally "const", they are set only once, in 61 // ActivateStaticTrials() below. 62 63 // Whether the static field trials have been initialized by 64 // ActivateStaticTrials() method. 65 bool static_field_trials_initialized = false; 66 67 // Field trial ID for the HistoryURL provider cull redirects experiment group. 68 int hup_dont_cull_redirects_experiment_group = 0; 69 70 // Field trial ID for the HistoryURL provider create shorter match 71 // experiment group. 72 int hup_dont_create_shorter_match_experiment_group = 0; 73 74 75 // Concatenates the autocomplete dynamic field trial prefix with a field trial 76 // ID to form a complete autocomplete field trial name. 77 std::string DynamicFieldTrialName(int id) { 78 return base::StringPrintf("%s%d", kAutocompleteDynamicFieldTrialPrefix, id); 79 } 80 81 } // namespace 82 83 84 void OmniboxFieldTrial::ActivateStaticTrials() { 85 DCHECK(!static_field_trials_initialized); 86 87 // Create the HistoryURL provider cull redirects field trial. 88 // Make it expire on March 1, 2013. 89 scoped_refptr<base::FieldTrial> trial( 90 base::FieldTrialList::FactoryGetFieldTrial( 91 kHUPCullRedirectsFieldTrialName, kHUPCullRedirectsFieldTrialDivisor, 92 "Standard", 2013, 3, 1, base::FieldTrial::ONE_TIME_RANDOMIZED, NULL)); 93 hup_dont_cull_redirects_experiment_group = 94 trial->AppendGroup("DontCullRedirects", 95 kHUPCullRedirectsFieldTrialExperimentFraction); 96 97 // Create the HistoryURL provider create shorter match field trial. 98 // Make it expire on March 1, 2013. 99 trial = base::FieldTrialList::FactoryGetFieldTrial( 100 kHUPCreateShorterMatchFieldTrialName, 101 kHUPCreateShorterMatchFieldTrialDivisor, "Standard", 2013, 3, 1, 102 base::FieldTrial::ONE_TIME_RANDOMIZED, NULL); 103 hup_dont_create_shorter_match_experiment_group = 104 trial->AppendGroup("DontCreateShorterMatch", 105 kHUPCreateShorterMatchFieldTrialExperimentFraction); 106 107 static_field_trials_initialized = true; 108 } 109 110 void OmniboxFieldTrial::ActivateDynamicTrials() { 111 // Initialize all autocomplete dynamic field trials. This method may be 112 // called multiple times. 113 for (int i = 0; i < kMaxAutocompleteDynamicFieldTrials; ++i) 114 base::FieldTrialList::FindValue(DynamicFieldTrialName(i)); 115 } 116 117 int OmniboxFieldTrial::GetDisabledProviderTypes() { 118 // Make sure that Autocomplete dynamic field trials are activated. It's OK to 119 // call this method multiple times. 120 ActivateDynamicTrials(); 121 122 // Look for group names in form of "DisabledProviders_<mask>" where "mask" 123 // is a bitmap of disabled provider types (AutocompleteProvider::Type). 124 int provider_types = 0; 125 for (int i = 0; i < kMaxAutocompleteDynamicFieldTrials; ++i) { 126 std::string group_name = base::FieldTrialList::FindFullName( 127 DynamicFieldTrialName(i)); 128 const char kDisabledProviders[] = "DisabledProviders_"; 129 if (!StartsWithASCII(group_name, kDisabledProviders, true)) 130 continue; 131 int types = 0; 132 if (!base::StringToInt(base::StringPiece( 133 group_name.substr(strlen(kDisabledProviders))), &types)) 134 continue; 135 provider_types |= types; 136 } 137 return provider_types; 138 } 139 140 void OmniboxFieldTrial::GetActiveSuggestFieldTrialHashes( 141 std::vector<uint32>* field_trial_hashes) { 142 field_trial_hashes->clear(); 143 for (int i = 0; i < kMaxAutocompleteDynamicFieldTrials; ++i) { 144 const std::string& trial_name = DynamicFieldTrialName(i); 145 if (base::FieldTrialList::TrialExists(trial_name)) 146 field_trial_hashes->push_back(metrics::HashName(trial_name)); 147 } 148 } 149 150 bool OmniboxFieldTrial::InHUPCullRedirectsFieldTrial() { 151 return base::FieldTrialList::TrialExists(kHUPCullRedirectsFieldTrialName); 152 } 153 154 bool OmniboxFieldTrial::InHUPCullRedirectsFieldTrialExperimentGroup() { 155 if (!base::FieldTrialList::TrialExists(kHUPCullRedirectsFieldTrialName)) 156 return false; 157 158 // Return true if we're in the experiment group. 159 const int group = base::FieldTrialList::FindValue( 160 kHUPCullRedirectsFieldTrialName); 161 return group == hup_dont_cull_redirects_experiment_group; 162 } 163 164 bool OmniboxFieldTrial::InHUPCreateShorterMatchFieldTrial() { 165 return 166 base::FieldTrialList::TrialExists(kHUPCreateShorterMatchFieldTrialName); 167 } 168 169 bool OmniboxFieldTrial::InHUPCreateShorterMatchFieldTrialExperimentGroup() { 170 if (!base::FieldTrialList::TrialExists(kHUPCreateShorterMatchFieldTrialName)) 171 return false; 172 173 // Return true if we're in the experiment group. 174 const int group = base::FieldTrialList::FindValue( 175 kHUPCreateShorterMatchFieldTrialName); 176 return group == hup_dont_create_shorter_match_experiment_group; 177 } 178 179 bool OmniboxFieldTrial::InStopTimerFieldTrialExperimentGroup() { 180 return (base::FieldTrialList::FindFullName(kStopTimerFieldTrialName) == 181 kStopTimerExperimentGroupName); 182 } 183 184 bool OmniboxFieldTrial::InZeroSuggestFieldTrial() { 185 // Make sure that Autocomplete dynamic field trials are activated. It's OK to 186 // call this method multiple times. 187 ActivateDynamicTrials(); 188 189 // Look for group names starting with "EnableZeroSuggest" 190 for (int i = 0; i < kMaxAutocompleteDynamicFieldTrials; ++i) { 191 const std::string& group_name = base::FieldTrialList::FindFullName( 192 DynamicFieldTrialName(i)); 193 const char kEnableZeroSuggest[] = "EnableZeroSuggest"; 194 if (StartsWithASCII(group_name, kEnableZeroSuggest, true)) 195 return true; 196 } 197 return false; 198 } 199 200 bool OmniboxFieldTrial::ShortcutsScoringMaxRelevance( 201 AutocompleteInput::PageClassification current_page_classification, 202 int* max_relevance) { 203 // The value of the rule is a string that encodes an integer containing 204 // the max relevance. 205 const std::string& max_relevance_str = 206 OmniboxFieldTrial::GetValueForRuleInContext( 207 kShortcutsScoringMaxRelevanceRule, current_page_classification); 208 if (max_relevance_str.empty()) 209 return false; 210 if (!base::StringToInt(max_relevance_str, max_relevance)) 211 return false; 212 return true; 213 } 214 215 bool OmniboxFieldTrial::SearchHistoryPreventInlining( 216 AutocompleteInput::PageClassification current_page_classification) { 217 return OmniboxFieldTrial::GetValueForRuleInContext( 218 kSearchHistoryRule, current_page_classification) == "PreventInlining"; 219 } 220 221 bool OmniboxFieldTrial::SearchHistoryDisable( 222 AutocompleteInput::PageClassification current_page_classification) { 223 return OmniboxFieldTrial::GetValueForRuleInContext( 224 kSearchHistoryRule, current_page_classification) == "Disable"; 225 } 226 227 void OmniboxFieldTrial::GetDemotionsByType( 228 AutocompleteInput::PageClassification current_page_classification, 229 DemotionMultipliers* demotions_by_type) { 230 demotions_by_type->clear(); 231 const std::string demotion_rule = 232 OmniboxFieldTrial::GetValueForRuleInContext( 233 kDemoteByTypeRule, 234 current_page_classification); 235 // The value of the DemoteByType rule is a comma-separated list of 236 // {ResultType + ":" + Number} where ResultType is an AutocompleteMatchType:: 237 // Type enum represented as an integer and Number is an integer number 238 // between 0 and 100 inclusive. Relevance scores of matches of that result 239 // type are multiplied by Number / 100. 100 means no change. 240 base::StringPairs kv_pairs; 241 if (base::SplitStringIntoKeyValuePairs(demotion_rule, ':', ',', &kv_pairs)) { 242 for (base::StringPairs::const_iterator it = kv_pairs.begin(); 243 it != kv_pairs.end(); ++it) { 244 // This is a best-effort conversion; we trust the hand-crafted parameters 245 // downloaded from the server to be perfect. There's no need for handle 246 // errors smartly. 247 int k, v; 248 base::StringToInt(it->first, &k); 249 base::StringToInt(it->second, &v); 250 (*demotions_by_type)[static_cast<AutocompleteMatchType::Type>(k)] = 251 static_cast<float>(v) / 100.0f; 252 } 253 } 254 } 255 256 bool OmniboxFieldTrial::ReorderForLegalDefaultMatch( 257 AutocompleteInput::PageClassification current_page_classification) { 258 return OmniboxFieldTrial::GetValueForRuleInContext( 259 kReorderForLegalDefaultMatchRule, current_page_classification) == 260 kReorderForLegalDefaultMatchRuleEnabled; 261 } 262 263 const char OmniboxFieldTrial::kBundledExperimentFieldTrialName[] = 264 "OmniboxBundledExperimentV1"; 265 const char OmniboxFieldTrial::kShortcutsScoringMaxRelevanceRule[] = 266 "ShortcutsScoringMaxRelevance"; 267 const char OmniboxFieldTrial::kSearchHistoryRule[] = "SearchHistory"; 268 const char OmniboxFieldTrial::kDemoteByTypeRule[] = "DemoteByType"; 269 const char OmniboxFieldTrial::kReorderForLegalDefaultMatchRule[] = 270 "ReorderForLegalDefaultMatch"; 271 const char OmniboxFieldTrial::kReorderForLegalDefaultMatchRuleEnabled[] = 272 "ReorderForLegalDefaultMatch"; 273 274 // Background and implementation details: 275 // 276 // Each experiment group in any field trial can come with an optional set of 277 // parameters (key-value pairs). In the bundled omnibox experiment 278 // (kBundledExperimentFieldTrialName), each experiment group comes with a 279 // list of parameters in the form: 280 // key=<Rule>: 281 // <AutocompleteInput::PageClassification (as an int)>: 282 // <whether Instant Extended is enabled (as a 1 or 0)> 283 // (note that there are no linebreaks in keys; this format is for 284 // presentation only> 285 // value=<arbitrary string> 286 // Both the AutocompleteInput::PageClassification and the Instant Extended 287 // entries can be "*", which means this rule applies for all values of the 288 // matching portion of the context. 289 // One example parameter is 290 // key=SearchHistory:6:1 291 // value=PreventInlining 292 // This means in page classification context 6 (a search result page doing 293 // search term replacement) with Instant Extended enabled, the SearchHistory 294 // experiment should PreventInlining. 295 // 296 // When an exact match to the rule in the current context is missing, we 297 // give preference to a wildcard rule that matches the instant extended 298 // context over a wildcard rule that matches the page classification 299 // context. Hopefully, though, users will write their field trial configs 300 // so as not to rely on this fall back order. 301 // 302 // In short, this function tries to find the value associated with key 303 // |rule|:|page_classification|:|instant_extended|, failing that it looks up 304 // |rule|:*:|instant_extended|, failing that it looks up 305 // |rule|:|page_classification|:*, failing that it looks up |rule|:*:*, 306 // and failing that it returns the empty string. 307 std::string OmniboxFieldTrial::GetValueForRuleInContext( 308 const std::string& rule, 309 AutocompleteInput::PageClassification page_classification) { 310 std::map<std::string, std::string> params; 311 if (!chrome_variations::GetVariationParams(kBundledExperimentFieldTrialName, 312 ¶ms)) { 313 return std::string(); 314 } 315 const std::string page_classification_str = 316 base::IntToString(static_cast<int>(page_classification)); 317 const std::string instant_extended = 318 chrome::IsInstantExtendedAPIEnabled() ? "1" : "0"; 319 // Look up rule in this exact context. 320 std::map<std::string, std::string>::iterator it = params.find( 321 rule + ":" + page_classification_str + ":" + instant_extended); 322 if (it != params.end()) 323 return it->second; 324 // Fall back to the global page classification context. 325 it = params.find(rule + ":*:" + instant_extended); 326 if (it != params.end()) 327 return it->second; 328 // Fall back to the global instant extended context. 329 it = params.find(rule + ":" + page_classification_str + ":*"); 330 if (it != params.end()) 331 return it->second; 332 // Look up rule in the global context. 333 it = params.find(rule + ":*:*"); 334 return (it != params.end()) ? it->second : std::string(); 335 } 336