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