1// Copyright 2013 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 "components/variations/variations_seed_processor.h"
6
7#include <map>
8#include <vector>
9
10#include "base/command_line.h"
11#include "base/metrics/field_trial.h"
12#include "base/strings/utf_string_conversions.h"
13#include "components/variations/processed_study.h"
14#include "components/variations/study_filtering.h"
15#include "components/variations/variations_associated_data.h"
16
17namespace variations {
18
19namespace {
20
21// Associates the variations params of |experiment|, if present.
22void RegisterExperimentParams(const Study& study,
23                              const Study_Experiment& experiment) {
24  std::map<std::string, std::string> params;
25  for (int i = 0; i < experiment.param_size(); ++i) {
26    if (experiment.param(i).has_name() && experiment.param(i).has_value())
27      params[experiment.param(i).name()] = experiment.param(i).value();
28  }
29  if (!params.empty())
30    AssociateVariationParams(study.name(), experiment.name(), params);
31}
32
33// If there are variation ids associated with |experiment|, register the
34// variation ids.
35void RegisterVariationIds(const Study_Experiment& experiment,
36                          const std::string& trial_name) {
37  if (experiment.has_google_web_experiment_id()) {
38    const VariationID variation_id =
39        static_cast<VariationID>(experiment.google_web_experiment_id());
40    AssociateGoogleVariationIDForce(GOOGLE_WEB_PROPERTIES,
41                                    trial_name,
42                                    experiment.name(),
43                                    variation_id);
44  }
45  if (experiment.has_google_web_trigger_experiment_id()) {
46    const VariationID variation_id =
47        static_cast<VariationID>(experiment.google_web_trigger_experiment_id());
48    AssociateGoogleVariationIDForce(GOOGLE_WEB_PROPERTIES_TRIGGER,
49                                    trial_name,
50                                    experiment.name(),
51                                    variation_id);
52  }
53  if (experiment.has_google_update_experiment_id()) {
54    const VariationID variation_id =
55        static_cast<VariationID>(experiment.google_update_experiment_id());
56    AssociateGoogleVariationIDForce(GOOGLE_UPDATE_SERVICE,
57                                    trial_name,
58                                    experiment.name(),
59                                    variation_id);
60  }
61}
62
63// Executes |callback| on every override defined by |experiment|.
64void ApplyUIStringOverrides(
65    const Study_Experiment& experiment,
66    const VariationsSeedProcessor::UIStringOverrideCallback& callback) {
67  for (int i = 0; i < experiment.override_ui_string_size(); ++i) {
68    const Study_Experiment_OverrideUIString& override =
69        experiment.override_ui_string(i);
70    callback.Run(override.name_hash(), base::UTF8ToUTF16(override.value()));
71  }
72}
73
74}  // namespace
75
76VariationsSeedProcessor::VariationsSeedProcessor() {
77}
78
79VariationsSeedProcessor::~VariationsSeedProcessor() {
80}
81
82void VariationsSeedProcessor::CreateTrialsFromSeed(
83    const VariationsSeed& seed,
84    const std::string& locale,
85    const base::Time& reference_date,
86    const base::Version& version,
87    Study_Channel channel,
88    Study_FormFactor form_factor,
89    const std::string& hardware_class,
90    const UIStringOverrideCallback& override_callback) {
91  std::vector<ProcessedStudy> filtered_studies;
92  FilterAndValidateStudies(seed, locale, reference_date, version, channel,
93                           form_factor, hardware_class, &filtered_studies);
94
95  for (size_t i = 0; i < filtered_studies.size(); ++i)
96    CreateTrialFromStudy(filtered_studies[i], override_callback);
97}
98
99void VariationsSeedProcessor::CreateTrialFromStudy(
100    const ProcessedStudy& processed_study,
101    const UIStringOverrideCallback& override_callback) {
102  const Study& study = *processed_study.study();
103
104  // Check if any experiments need to be forced due to a command line
105  // flag. Force the first experiment with an existing flag.
106  CommandLine* command_line = CommandLine::ForCurrentProcess();
107  for (int i = 0; i < study.experiment_size(); ++i) {
108    const Study_Experiment& experiment = study.experiment(i);
109    if (experiment.has_forcing_flag() &&
110        command_line->HasSwitch(experiment.forcing_flag())) {
111      scoped_refptr<base::FieldTrial> trial(
112          base::FieldTrialList::CreateFieldTrial(study.name(),
113                                                 experiment.name()));
114      // If |trial| is NULL, then there might already be a trial forced to a
115      // different group (e.g. via --force-fieldtrials). Break out of the loop,
116      // but don't return, so that variation ids and params for the selected
117      // group will still be picked up.
118      if (!trial.get())
119        break;
120
121      RegisterExperimentParams(study, experiment);
122      RegisterVariationIds(experiment, study.name());
123      if (study.activation_type() == Study_ActivationType_ACTIVATION_AUTO) {
124        trial->group();
125        // UI Strings can only be overridden from ACTIVATION_AUTO experiments.
126        ApplyUIStringOverrides(experiment, override_callback);
127      }
128
129      DVLOG(1) << "Trial " << study.name() << " forced by flag: "
130               << experiment.forcing_flag();
131      return;
132    }
133  }
134
135  uint32 randomization_seed = 0;
136  base::FieldTrial::RandomizationType randomization_type =
137      base::FieldTrial::SESSION_RANDOMIZED;
138  if (study.has_consistency() &&
139      study.consistency() == Study_Consistency_PERMANENT) {
140    randomization_type = base::FieldTrial::ONE_TIME_RANDOMIZED;
141    if (study.has_randomization_seed())
142      randomization_seed = study.randomization_seed();
143  }
144
145  // The trial is created without specifying an expiration date because the
146  // expiration check in field_trial.cc is based on the build date. Instead,
147  // the expiration check using |reference_date| is done explicitly below.
148  scoped_refptr<base::FieldTrial> trial(
149      base::FieldTrialList::FactoryGetFieldTrialWithRandomizationSeed(
150          study.name(), processed_study.total_probability(),
151          study.default_experiment_name(),
152          base::FieldTrialList::kNoExpirationYear, 1, 1, randomization_type,
153          randomization_seed, NULL));
154
155  bool has_overrides = false;
156  for (int i = 0; i < study.experiment_size(); ++i) {
157    const Study_Experiment& experiment = study.experiment(i);
158    RegisterExperimentParams(study, experiment);
159
160    // Groups with forcing flags have probability 0 and will never be selected.
161    // Therefore, there's no need to add them to the field trial.
162    if (experiment.has_forcing_flag())
163      continue;
164
165    if (experiment.name() != study.default_experiment_name())
166      trial->AppendGroup(experiment.name(), experiment.probability_weight());
167
168    RegisterVariationIds(experiment, study.name());
169
170    has_overrides = has_overrides || experiment.override_ui_string_size() > 0;
171  }
172
173  trial->SetForced();
174  if (processed_study.is_expired()) {
175    trial->Disable();
176  } else if (study.activation_type() == Study_ActivationType_ACTIVATION_AUTO) {
177    const std::string& group_name = trial->group_name();
178
179    // Don't try to apply overrides if none of the experiments in this study had
180    // any.
181    if (!has_overrides)
182      return;
183
184    // UI Strings can only be overridden from ACTIVATION_AUTO experiments.
185    int experiment_index = processed_study.GetExperimentIndexByName(group_name);
186
187    // The field trial was defined from |study|, so the active experiment's name
188    // must be in the |study|.
189    DCHECK_NE(-1, experiment_index);
190
191    ApplyUIStringOverrides(study.experiment(experiment_index),
192                           override_callback);
193  }
194}
195
196}  // namespace variations
197