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
24using base::FieldTrial;
25using base::FieldTrialList;
26using base::SplitStringUsingSubstr;
27using base::StringToInt;
28using std::string;
29using std::vector;
30
31namespace prerender {
32
33namespace {
34
35const char kOmniboxTrialName[] = "PrerenderFromOmnibox";
36int g_omnibox_trial_default_group_number = kint32min;
37
38const char kDisabledGroup[] = "Disabled";
39const char kEnabledGroup[] = "Enabled";
40
41const char kLocalPredictorSpecTrialName[] = "PrerenderLocalPredictorSpec";
42const char kLocalPredictorKeyName[] = "LocalPredictor";
43const char kLocalPredictorUnencryptedSyncOnlyKeyName[] =
44    "LocalPredictorUnencryptedSyncOnly";
45const char kSideEffectFreeWhitelistKeyName[] = "SideEffectFreeWhitelist";
46const char kPrerenderLaunchKeyName[] = "PrerenderLaunch";
47const char kPrerenderAlwaysControlKeyName[] = "PrerenderAlwaysControl";
48const char kPrerenderQueryPrerenderServiceKeyName[] =
49    "PrerenderQueryPrerenderService";
50const char kPrerenderQueryPrerenderServiceCurrentURLKeyName[] =
51    "PrerenderQueryPrerenderServiceCurrentURL";
52const char kPrerenderQueryPrerenderServiceCandidateURLsKeyName[] =
53    "PrerenderQueryPrerenderServiceCandidateURLs";
54const char kPrerenderServiceBehaviorIDKeyName[] = "PrerenderServiceBehaviorID";
55const char kPrerenderServiceFetchTimeoutKeyName[] =
56    "PrerenderServiceFetchTimeoutMs";
57const char kPrerenderTTLKeyName[] = "PrerenderTTLSeconds";
58const char kPrerenderPriorityHalfLifeTimeKeyName[] =
59    "PrerenderPriorityHalfLifeTimeSeconds";
60const char kMaxConcurrentPrerenderKeyName[] = "MaxConcurrentPrerenders";
61const char kSkipFragment[] = "SkipFragment";
62const char kSkipHTTPS[] = "SkipHTTPS";
63const char kSkipWhitelist[] = "SkipWhitelist";
64const char kSkipServiceWhitelist[] = "SkipServiceWhitelist";
65const char kSkipLoggedIn[] = "SkipLoggedIn";
66const char kSkipDefaultNoPrerender[] = "SkipDefaultNoPrerender";
67const char kPrerenderServiceURLPrefixParameterName[] =
68    "PrerenderServiceURLPrefix";
69const char kDefaultPrerenderServiceURLPrefix[] =
70    "https://clients4.google.com/prerenderservice/?q=";
71const int kMinPrerenderServiceTimeoutMs = 1;
72const int kMaxPrerenderServiceTimeoutMs = 10000;
73const int kDefaultPrerenderServiceTimeoutMs = 1000;
74const char kSkipPrerenderLocalCanadidates[] = "SkipPrerenderLocalCandidates";
75const char kSkipPrerenderServiceCanadidates[] =
76    "SkipPrerenderServiceCandidates";
77const char kDisableSessionStorageNamespaceMerging[] =
78    "DisableSessionStorageNamespaceMerging";
79
80void 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
174void ConfigureOmniboxPrerender();
175
176void 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
222void 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
241bool 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/*
270PrerenderLocalPredictorSpec is a field trial, and its value must have the
271following format:
272key1=value1:key2=value2:key3=value3
273eg "LocalPredictor=Enabled:SideEffectFreeWhitelist=Enabled"
274The function below extracts the value corresponding to a key provided from the
275LocalPredictorSpec.
276*/
277string 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
292bool 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.
301bool 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
312bool DisableLocalPredictorBasedOnSyncAndConfiguration(Profile* profile) {
313  return
314      GetLocalPredictorSpecValue(kLocalPredictorUnencryptedSyncOnlyKeyName) ==
315      kEnabledGroup &&
316      !IsUnencryptedSyncEnabled(profile);
317}
318
319bool IsLoggedInPredictorEnabled() {
320  return IsLocalPredictorEnabled();
321}
322
323bool IsSideEffectFreeWhitelistEnabled() {
324  return IsLocalPredictorEnabled() &&
325      GetLocalPredictorSpecValue(kSideEffectFreeWhitelistKeyName) !=
326      kDisabledGroup;
327}
328
329bool IsLocalPredictorPrerenderLaunchEnabled() {
330  return GetLocalPredictorSpecValue(kPrerenderLaunchKeyName) != kDisabledGroup;
331}
332
333bool IsLocalPredictorPrerenderAlwaysControlEnabled() {
334  return GetLocalPredictorSpecValue(kPrerenderAlwaysControlKeyName) ==
335      kEnabledGroup;
336}
337
338bool ShouldQueryPrerenderService(Profile* profile) {
339  return IsUnencryptedSyncEnabled(profile) &&
340      GetLocalPredictorSpecValue(kPrerenderQueryPrerenderServiceKeyName) ==
341      kEnabledGroup;
342}
343
344bool ShouldQueryPrerenderServiceForCurrentURL() {
345  return GetLocalPredictorSpecValue(
346      kPrerenderQueryPrerenderServiceCurrentURLKeyName) != kDisabledGroup;
347}
348
349bool ShouldQueryPrerenderServiceForCandidateURLs() {
350  return GetLocalPredictorSpecValue(
351      kPrerenderQueryPrerenderServiceCandidateURLsKeyName) != kDisabledGroup;
352}
353
354string GetPrerenderServiceURLPrefix() {
355  string prefix = chrome_variations::GetVariationParamValue(
356      kLocalPredictorSpecTrialName,
357      kPrerenderServiceURLPrefixParameterName);
358  if (prefix.empty())
359    prefix = kDefaultPrerenderServiceURLPrefix;
360  return prefix;
361}
362
363int 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
373int 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
385int 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
394int 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
404int 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
417bool SkipLocalPredictorFragment() {
418  return GetLocalPredictorSpecValue(kSkipFragment) == kEnabledGroup;
419}
420
421bool SkipLocalPredictorHTTPS() {
422  return GetLocalPredictorSpecValue(kSkipHTTPS) == kEnabledGroup;
423}
424
425bool SkipLocalPredictorWhitelist() {
426  return GetLocalPredictorSpecValue(kSkipWhitelist) == kEnabledGroup;
427}
428
429bool SkipLocalPredictorServiceWhitelist() {
430  return GetLocalPredictorSpecValue(kSkipServiceWhitelist) == kEnabledGroup;
431}
432
433bool SkipLocalPredictorLoggedIn() {
434  return GetLocalPredictorSpecValue(kSkipLoggedIn) == kEnabledGroup;
435}
436
437bool SkipLocalPredictorDefaultNoPrerender() {
438  return GetLocalPredictorSpecValue(kSkipDefaultNoPrerender) == kEnabledGroup;
439}
440
441bool SkipLocalPredictorLocalCandidates() {
442  return GetLocalPredictorSpecValue(kSkipPrerenderLocalCanadidates) ==
443      kEnabledGroup;
444}
445
446bool SkipLocalPredictorServiceCandidates() {
447  return GetLocalPredictorSpecValue(kSkipPrerenderServiceCanadidates) ==
448      kEnabledGroup;
449}
450
451bool ShouldMergeSessionStorageNamespaces() {
452  return GetLocalPredictorSpecValue(kDisableSessionStorageNamespaceMerging) !=
453      kDisabledGroup;
454}
455
456}  // namespace prerender
457