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/prerender/prerender_field_trial.h" 6 7 #include "base/command_line.h" 8 #include "base/logging.h" 9 #include "base/metrics/field_trial.h" 10 #include "base/metrics/histogram.h" 11 #include "base/prefs/pref_service.h" 12 #include "base/strings/string_number_conversions.h" 13 #include "base/strings/string_split.h" 14 #include "chrome/browser/predictors/autocomplete_action_predictor.h" 15 #include "chrome/browser/prerender/prerender_manager.h" 16 #include "chrome/browser/profiles/profile.h" 17 #include "chrome/browser/sync/profile_sync_service.h" 18 #include "chrome/browser/sync/profile_sync_service_factory.h" 19 #include "chrome/common/chrome_switches.h" 20 #include "chrome/common/chrome_version_info.h" 21 #include "components/metrics/metrics_service.h" 22 #include "components/variations/variations_associated_data.h" 23 24 using base::FieldTrial; 25 using base::FieldTrialList; 26 using base::StringToInt; 27 using std::string; 28 using std::vector; 29 30 namespace prerender { 31 32 namespace { 33 34 const char kOmniboxTrialName[] = "PrerenderFromOmnibox"; 35 int g_omnibox_trial_default_group_number = kint32min; 36 37 const char kDisabledGroup[] = "Disabled"; 38 const char kEnabledGroup[] = "Enabled"; 39 40 const char kLocalPredictorSpecTrialName[] = "PrerenderLocalPredictorSpec"; 41 const char kLocalPredictorKeyName[] = "LocalPredictor"; 42 const char kLocalPredictorUnencryptedSyncOnlyKeyName[] = 43 "LocalPredictorUnencryptedSyncOnly"; 44 const char kSideEffectFreeWhitelistKeyName[] = "SideEffectFreeWhitelist"; 45 const char kPrerenderLaunchKeyName[] = "PrerenderLaunch"; 46 const char kPrerenderAlwaysControlKeyName[] = "PrerenderAlwaysControl"; 47 const char kPrerenderPrefetchKeyName[] = "PrerenderPrefetch"; 48 const char kPrerenderQueryPrerenderServiceKeyName[] = 49 "PrerenderQueryPrerenderService"; 50 const char kPrerenderQueryPrerenderServiceCurrentURLKeyName[] = 51 "PrerenderQueryPrerenderServiceCurrentURL"; 52 const char kPrerenderQueryPrerenderServiceCandidateURLsKeyName[] = 53 "PrerenderQueryPrerenderServiceCandidateURLs"; 54 const char kPrerenderServiceBehaviorIDKeyName[] = "PrerenderServiceBehaviorID"; 55 const char kPrerenderServiceFetchTimeoutKeyName[] = 56 "PrerenderServiceFetchTimeoutMs"; 57 const char kPrerenderTTLKeyName[] = "PrerenderTTLSeconds"; 58 const char kPrerenderPriorityHalfLifeTimeKeyName[] = 59 "PrerenderPriorityHalfLifeTimeSeconds"; 60 const char kMaxConcurrentPrerenderKeyName[] = "MaxConcurrentPrerenders"; 61 const char kMaxLaunchPrerenderKeyName[] = "MaxLaunchPrerenders"; 62 const char kSkipFragment[] = "SkipFragment"; 63 const char kSkipHTTPS[] = "SkipHTTPS"; 64 const char kSkipWhitelist[] = "SkipWhitelist"; 65 const char kSkipServiceWhitelist[] = "SkipServiceWhitelist"; 66 const char kSkipLoggedIn[] = "SkipLoggedIn"; 67 const char kSkipDefaultNoPrerender[] = "SkipDefaultNoPrerender"; 68 const char kPrerenderServiceURLPrefixParameterName[] = 69 "PrerenderServiceURLPrefix"; 70 const char kDefaultPrerenderServiceURLPrefix[] = 71 "https://clients4.google.com/prerenderservice/?q="; 72 const int kMinPrerenderServiceTimeoutMs = 1; 73 const int kMaxPrerenderServiceTimeoutMs = 10000; 74 const int kDefaultPrerenderServiceTimeoutMs = 1000; 75 const char kSkipPrerenderLocalCanadidates[] = "SkipPrerenderLocalCandidates"; 76 const char kSkipPrerenderServiceCanadidates[] = 77 "SkipPrerenderServiceCandidates"; 78 const char kDisableSessionStorageNamespaceMerging[] = 79 "DisableSessionStorageNamespaceMerging"; 80 const char kPrerenderCookieStore[] = "PrerenderCookieStore"; 81 82 void SetupPrerenderFieldTrial() { 83 const FieldTrial::Probability divisor = 1000; 84 85 FieldTrial::Probability control_probability; 86 FieldTrial::Probability experiment_multi_prerender_probability; 87 FieldTrial::Probability experiment_15min_ttl_probability; 88 FieldTrial::Probability experiment_no_use_probability; 89 90 chrome::VersionInfo::Channel channel = chrome::VersionInfo::GetChannel(); 91 if (channel == chrome::VersionInfo::CHANNEL_STABLE || 92 channel == chrome::VersionInfo::CHANNEL_BETA) { 93 // Use very conservatives and stable settings in beta and stable. 94 const FieldTrial::Probability release_prerender_enabled_probability = 980; 95 const FieldTrial::Probability release_control_probability = 10; 96 const FieldTrial::Probability 97 release_experiment_multi_prerender_probability = 0; 98 const FieldTrial::Probability release_experiment_15min_ttl_probability = 10; 99 const FieldTrial::Probability release_experiment_no_use_probability = 0; 100 COMPILE_ASSERT( 101 release_prerender_enabled_probability + release_control_probability + 102 release_experiment_multi_prerender_probability + 103 release_experiment_15min_ttl_probability + 104 release_experiment_no_use_probability == divisor, 105 release_experiment_probabilities_must_equal_divisor); 106 107 control_probability = release_control_probability; 108 experiment_multi_prerender_probability = 109 release_experiment_multi_prerender_probability; 110 experiment_15min_ttl_probability = release_experiment_15min_ttl_probability; 111 experiment_no_use_probability = release_experiment_no_use_probability; 112 } else { 113 // In testing channels, use more experiments and a larger control group to 114 // improve quality of data. 115 const FieldTrial::Probability dev_prerender_enabled_probability = 250; 116 const FieldTrial::Probability dev_control_probability = 250; 117 const FieldTrial::Probability 118 dev_experiment_multi_prerender_probability = 250; 119 const FieldTrial::Probability dev_experiment_15min_ttl_probability = 125; 120 const FieldTrial::Probability dev_experiment_no_use_probability = 125; 121 COMPILE_ASSERT(dev_prerender_enabled_probability + dev_control_probability + 122 dev_experiment_multi_prerender_probability + 123 dev_experiment_15min_ttl_probability + 124 dev_experiment_no_use_probability == divisor, 125 dev_experiment_probabilities_must_equal_divisor); 126 127 control_probability = dev_control_probability; 128 experiment_multi_prerender_probability = 129 dev_experiment_multi_prerender_probability; 130 experiment_15min_ttl_probability = dev_experiment_15min_ttl_probability; 131 experiment_no_use_probability = dev_experiment_no_use_probability; 132 } 133 134 int prerender_enabled_group = -1; 135 scoped_refptr<FieldTrial> trial( 136 FieldTrialList::FactoryGetFieldTrial( 137 "Prerender", divisor, "PrerenderEnabled", 138 2014, 12, 31, FieldTrial::SESSION_RANDOMIZED, 139 &prerender_enabled_group)); 140 const int control_group = 141 trial->AppendGroup("PrerenderControl", 142 control_probability); 143 const int experiment_multi_prerender_group = 144 trial->AppendGroup("PrerenderMulti", 145 experiment_multi_prerender_probability); 146 const int experiment_15_min_TTL_group = 147 trial->AppendGroup("Prerender15minTTL", 148 experiment_15min_ttl_probability); 149 const int experiment_no_use_group = 150 trial->AppendGroup("PrerenderNoUse", 151 experiment_no_use_probability); 152 153 const int trial_group = trial->group(); 154 if (trial_group == prerender_enabled_group) { 155 PrerenderManager::SetMode( 156 PrerenderManager::PRERENDER_MODE_EXPERIMENT_PRERENDER_GROUP); 157 } else if (trial_group == control_group) { 158 PrerenderManager::SetMode( 159 PrerenderManager::PRERENDER_MODE_EXPERIMENT_CONTROL_GROUP); 160 } else if (trial_group == experiment_multi_prerender_group) { 161 PrerenderManager::SetMode( 162 PrerenderManager::PRERENDER_MODE_EXPERIMENT_MULTI_PRERENDER_GROUP); 163 } else if (trial_group == experiment_15_min_TTL_group) { 164 PrerenderManager::SetMode( 165 PrerenderManager::PRERENDER_MODE_EXPERIMENT_15MIN_TTL_GROUP); 166 } else if (trial_group == experiment_no_use_group) { 167 PrerenderManager::SetMode( 168 PrerenderManager::PRERENDER_MODE_EXPERIMENT_NO_USE_GROUP); 169 } else { 170 NOTREACHED(); 171 } 172 } 173 174 } // end namespace 175 176 void ConfigureOmniboxPrerender(); 177 178 void ConfigurePrerender(const CommandLine& command_line) { 179 enum PrerenderOption { 180 PRERENDER_OPTION_AUTO, 181 PRERENDER_OPTION_DISABLED, 182 PRERENDER_OPTION_ENABLED, 183 }; 184 185 PrerenderOption prerender_option = PRERENDER_OPTION_AUTO; 186 if (command_line.HasSwitch(switches::kPrerenderMode)) { 187 const string switch_value = 188 command_line.GetSwitchValueASCII(switches::kPrerenderMode); 189 190 if (switch_value == switches::kPrerenderModeSwitchValueAuto) { 191 prerender_option = PRERENDER_OPTION_AUTO; 192 } else if (switch_value == switches::kPrerenderModeSwitchValueDisabled) { 193 prerender_option = PRERENDER_OPTION_DISABLED; 194 } else if (switch_value.empty() || 195 switch_value == switches::kPrerenderModeSwitchValueEnabled) { 196 // The empty string means the option was provided with no value, and that 197 // means enable. 198 prerender_option = PRERENDER_OPTION_ENABLED; 199 } else { 200 prerender_option = PRERENDER_OPTION_DISABLED; 201 LOG(ERROR) << "Invalid --prerender option received on command line: " 202 << switch_value; 203 LOG(ERROR) << "Disabling prerendering!"; 204 } 205 } 206 207 switch (prerender_option) { 208 case PRERENDER_OPTION_AUTO: 209 SetupPrerenderFieldTrial(); 210 break; 211 case PRERENDER_OPTION_DISABLED: 212 PrerenderManager::SetMode(PrerenderManager::PRERENDER_MODE_DISABLED); 213 break; 214 case PRERENDER_OPTION_ENABLED: 215 PrerenderManager::SetMode(PrerenderManager::PRERENDER_MODE_ENABLED); 216 break; 217 default: 218 NOTREACHED(); 219 } 220 221 ConfigureOmniboxPrerender(); 222 } 223 224 void ConfigureOmniboxPrerender() { 225 // Field trial to see if we're enabled. 226 const FieldTrial::Probability kDivisor = 100; 227 228 FieldTrial::Probability kDisabledProbability = 10; 229 chrome::VersionInfo::Channel channel = chrome::VersionInfo::GetChannel(); 230 if (channel == chrome::VersionInfo::CHANNEL_STABLE || 231 channel == chrome::VersionInfo::CHANNEL_BETA) { 232 kDisabledProbability = 1; 233 } 234 scoped_refptr<FieldTrial> omnibox_prerender_trial( 235 FieldTrialList::FactoryGetFieldTrial( 236 kOmniboxTrialName, kDivisor, "OmniboxPrerenderEnabled", 237 2014, 12, 31, FieldTrial::SESSION_RANDOMIZED, 238 &g_omnibox_trial_default_group_number)); 239 omnibox_prerender_trial->AppendGroup("OmniboxPrerenderDisabled", 240 kDisabledProbability); 241 } 242 243 bool IsOmniboxEnabled(Profile* profile) { 244 if (!profile) 245 return false; 246 247 if (!PrerenderManager::IsPrerenderingPossible()) 248 return false; 249 250 // Override any field trial groups if the user has set a command line flag. 251 if (CommandLine::ForCurrentProcess()->HasSwitch( 252 switches::kPrerenderFromOmnibox)) { 253 const string switch_value = 254 CommandLine::ForCurrentProcess()->GetSwitchValueASCII( 255 switches::kPrerenderFromOmnibox); 256 257 if (switch_value == switches::kPrerenderFromOmniboxSwitchValueEnabled) 258 return true; 259 260 if (switch_value == switches::kPrerenderFromOmniboxSwitchValueDisabled) 261 return false; 262 263 DCHECK_EQ(switches::kPrerenderFromOmniboxSwitchValueAuto, switch_value); 264 } 265 266 const int group = FieldTrialList::FindValue(kOmniboxTrialName); 267 return group == FieldTrial::kNotFinalized || 268 group == g_omnibox_trial_default_group_number; 269 } 270 271 /* 272 PrerenderLocalPredictorSpec is a field trial, and its value must have the 273 following format: 274 key1=value1:key2=value2:key3=value3 275 eg "LocalPredictor=Enabled:SideEffectFreeWhitelist=Enabled" 276 The function below extracts the value corresponding to a key provided from the 277 LocalPredictorSpec. 278 */ 279 string GetLocalPredictorSpecValue(string spec_key) { 280 vector<string> elements; 281 base::SplitString(FieldTrialList::FindFullName(kLocalPredictorSpecTrialName), 282 ':', &elements); 283 for (int i = 0; i < static_cast<int>(elements.size()); i++) { 284 vector<string> key_value; 285 base::SplitString(elements[i], '=', &key_value); 286 if (key_value.size() == 2 && key_value[0] == spec_key) 287 return key_value[1]; 288 } 289 return string(); 290 } 291 292 bool IsUnencryptedSyncEnabled(Profile* profile) { 293 ProfileSyncService* service = ProfileSyncServiceFactory::GetInstance()-> 294 GetForProfile(profile); 295 return service && service->GetOpenTabsUIDelegate() && 296 !service->EncryptEverythingEnabled(); 297 } 298 299 // Indicates whether the Local Predictor is enabled based on field trial 300 // selection. 301 bool IsLocalPredictorEnabled() { 302 #if defined(OS_ANDROID) || defined(OS_IOS) 303 return false; 304 #endif 305 return 306 !CommandLine::ForCurrentProcess()->HasSwitch( 307 switches::kDisablePrerenderLocalPredictor) && 308 GetLocalPredictorSpecValue(kLocalPredictorKeyName) == kEnabledGroup; 309 } 310 311 bool DisableLocalPredictorBasedOnSyncAndConfiguration(Profile* profile) { 312 return 313 GetLocalPredictorSpecValue(kLocalPredictorUnencryptedSyncOnlyKeyName) == 314 kEnabledGroup && 315 !IsUnencryptedSyncEnabled(profile); 316 } 317 318 bool IsLoggedInPredictorEnabled() { 319 return IsLocalPredictorEnabled(); 320 } 321 322 bool IsSideEffectFreeWhitelistEnabled() { 323 return IsLocalPredictorEnabled() && 324 GetLocalPredictorSpecValue(kSideEffectFreeWhitelistKeyName) != 325 kDisabledGroup; 326 } 327 328 bool IsLocalPredictorPrerenderLaunchEnabled() { 329 return GetLocalPredictorSpecValue(kPrerenderLaunchKeyName) != kDisabledGroup; 330 } 331 332 bool IsLocalPredictorPrerenderAlwaysControlEnabled() { 333 // If we prefetch rather than prerender, we automatically also prerender 334 // as a control group only. 335 return (GetLocalPredictorSpecValue(kPrerenderAlwaysControlKeyName) == 336 kEnabledGroup) || IsLocalPredictorPrerenderPrefetchEnabled(); 337 } 338 339 bool IsLocalPredictorPrerenderPrefetchEnabled() { 340 return GetLocalPredictorSpecValue(kPrerenderPrefetchKeyName) == 341 kEnabledGroup; 342 } 343 344 bool ShouldQueryPrerenderService(Profile* profile) { 345 return IsUnencryptedSyncEnabled(profile) && 346 GetLocalPredictorSpecValue(kPrerenderQueryPrerenderServiceKeyName) == 347 kEnabledGroup; 348 } 349 350 bool ShouldQueryPrerenderServiceForCurrentURL() { 351 return GetLocalPredictorSpecValue( 352 kPrerenderQueryPrerenderServiceCurrentURLKeyName) != kDisabledGroup; 353 } 354 355 bool ShouldQueryPrerenderServiceForCandidateURLs() { 356 return GetLocalPredictorSpecValue( 357 kPrerenderQueryPrerenderServiceCandidateURLsKeyName) != kDisabledGroup; 358 } 359 360 string GetPrerenderServiceURLPrefix() { 361 string prefix = chrome_variations::GetVariationParamValue( 362 kLocalPredictorSpecTrialName, 363 kPrerenderServiceURLPrefixParameterName); 364 return prefix.empty() ? kDefaultPrerenderServiceURLPrefix : prefix; 365 } 366 367 int GetPrerenderServiceBehaviorID() { 368 int id; 369 StringToInt(GetLocalPredictorSpecValue(kPrerenderServiceBehaviorIDKeyName), 370 &id); 371 // The behavior ID must be non-negative. 372 return std::max(id, 0); 373 } 374 375 int GetPrerenderServiceFetchTimeoutMs() { 376 int result; 377 StringToInt(GetLocalPredictorSpecValue(kPrerenderServiceFetchTimeoutKeyName), 378 &result); 379 // If the value is outside the valid range, use the default value. 380 return (result < kMinPrerenderServiceTimeoutMs || 381 result > kMaxPrerenderServiceTimeoutMs) ? 382 kDefaultPrerenderServiceTimeoutMs : result; 383 } 384 385 int GetLocalPredictorTTLSeconds() { 386 int ttl; 387 StringToInt(GetLocalPredictorSpecValue(kPrerenderTTLKeyName), &ttl); 388 // If the value is outside of 10s or 600s, use a default value of 180s. 389 return (ttl < 10 || ttl > 600) ? 180 : ttl; 390 } 391 392 int GetLocalPredictorPrerenderPriorityHalfLifeTimeSeconds() { 393 int half_life_time; 394 StringToInt(GetLocalPredictorSpecValue(kPrerenderPriorityHalfLifeTimeKeyName), 395 &half_life_time); 396 // Sanity check: Ensure the half life time is non-negative. 397 return std::max(half_life_time, 0); 398 } 399 400 int GetLocalPredictorMaxConcurrentPrerenders() { 401 int num_prerenders; 402 StringToInt(GetLocalPredictorSpecValue(kMaxConcurrentPrerenderKeyName), 403 &num_prerenders); 404 // Sanity check: Ensure the number of prerenders is between 1 and 10. 405 return std::min(std::max(num_prerenders, 1), 10); 406 } 407 408 int GetLocalPredictorMaxLaunchPrerenders() { 409 int num_prerenders; 410 StringToInt(GetLocalPredictorSpecValue(kMaxLaunchPrerenderKeyName), 411 &num_prerenders); 412 // Sanity check: Ensure the number of prerenders is between 1 and 10. 413 return std::min(std::max(num_prerenders, 1), 10); 414 } 415 416 bool SkipLocalPredictorFragment() { 417 return GetLocalPredictorSpecValue(kSkipFragment) == kEnabledGroup; 418 } 419 420 bool SkipLocalPredictorHTTPS() { 421 return GetLocalPredictorSpecValue(kSkipHTTPS) == kEnabledGroup; 422 } 423 424 bool SkipLocalPredictorWhitelist() { 425 return GetLocalPredictorSpecValue(kSkipWhitelist) == kEnabledGroup; 426 } 427 428 bool SkipLocalPredictorServiceWhitelist() { 429 return GetLocalPredictorSpecValue(kSkipServiceWhitelist) == kEnabledGroup; 430 } 431 432 bool SkipLocalPredictorLoggedIn() { 433 return GetLocalPredictorSpecValue(kSkipLoggedIn) == kEnabledGroup; 434 } 435 436 bool SkipLocalPredictorDefaultNoPrerender() { 437 return GetLocalPredictorSpecValue(kSkipDefaultNoPrerender) == kEnabledGroup; 438 } 439 440 bool SkipLocalPredictorLocalCandidates() { 441 return GetLocalPredictorSpecValue(kSkipPrerenderLocalCanadidates) == 442 kEnabledGroup; 443 } 444 445 bool SkipLocalPredictorServiceCandidates() { 446 return GetLocalPredictorSpecValue(kSkipPrerenderServiceCanadidates) == 447 kEnabledGroup; 448 } 449 450 bool ShouldMergeSessionStorageNamespaces() { 451 return GetLocalPredictorSpecValue(kDisableSessionStorageNamespaceMerging) != 452 kDisabledGroup; 453 } 454 455 bool IsPrerenderCookieStoreEnabled() { 456 return GetLocalPredictorSpecValue(kPrerenderCookieStore) != kDisabledGroup && 457 FieldTrialList::FindFullName(kPrerenderCookieStore) != kDisabledGroup; 458 } 459 460 } // namespace prerender 461