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 ¶ms)) 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 ¶ms)) { 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