1// Copyright 2015 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 "base/feature_list.h"
6
7#include <stddef.h>
8
9#include <utility>
10#include <vector>
11
12#include "base/logging.h"
13#include "base/metrics/field_trial.h"
14#include "base/strings/string_split.h"
15#include "base/strings/string_util.h"
16
17namespace base {
18
19namespace {
20
21// Pointer to the FeatureList instance singleton that was set via
22// FeatureList::SetInstance(). Does not use base/memory/singleton.h in order to
23// have more control over initialization timing. Leaky.
24FeatureList* g_instance = nullptr;
25
26// Tracks whether the FeatureList instance was initialized via an accessor.
27bool g_initialized_from_accessor = false;
28
29// Some characters are not allowed to appear in feature names or the associated
30// field trial names, as they are used as special characters for command-line
31// serialization. This function checks that the strings are ASCII (since they
32// are used in command-line API functions that require ASCII) and whether there
33// are any reserved characters present, returning true if the string is valid.
34// Only called in DCHECKs.
35bool IsValidFeatureOrFieldTrialName(const std::string& name) {
36  return IsStringASCII(name) && name.find_first_of(",<*") == std::string::npos;
37}
38
39}  // namespace
40
41FeatureList::FeatureList() {}
42
43FeatureList::~FeatureList() {}
44
45void FeatureList::InitializeFromCommandLine(
46    const std::string& enable_features,
47    const std::string& disable_features) {
48  DCHECK(!initialized_);
49
50  // Process disabled features first, so that disabled ones take precedence over
51  // enabled ones (since RegisterOverride() uses insert()).
52  RegisterOverridesFromCommandLine(disable_features, OVERRIDE_DISABLE_FEATURE);
53  RegisterOverridesFromCommandLine(enable_features, OVERRIDE_ENABLE_FEATURE);
54
55  initialized_from_command_line_ = true;
56}
57
58bool FeatureList::IsFeatureOverriddenFromCommandLine(
59    const std::string& feature_name,
60    OverrideState state) const {
61  auto it = overrides_.find(feature_name);
62  return it != overrides_.end() && it->second.overridden_state == state &&
63         !it->second.overridden_by_field_trial;
64}
65
66void FeatureList::AssociateReportingFieldTrial(
67    const std::string& feature_name,
68    OverrideState for_overridden_state,
69    FieldTrial* field_trial) {
70  DCHECK(
71      IsFeatureOverriddenFromCommandLine(feature_name, for_overridden_state));
72
73  // Only one associated field trial is supported per feature. This is generally
74  // enforced server-side.
75  OverrideEntry* entry = &overrides_.find(feature_name)->second;
76  if (entry->field_trial) {
77    NOTREACHED() << "Feature " << feature_name
78                 << " already has trial: " << entry->field_trial->trial_name()
79                 << ", associating trial: " << field_trial->trial_name();
80    return;
81  }
82
83  entry->field_trial = field_trial;
84}
85
86void FeatureList::RegisterFieldTrialOverride(const std::string& feature_name,
87                                             OverrideState override_state,
88                                             FieldTrial* field_trial) {
89  DCHECK(field_trial);
90  DCHECK(!ContainsKey(overrides_, feature_name) ||
91         !overrides_.find(feature_name)->second.field_trial)
92      << "Feature " << feature_name
93      << " has conflicting field trial overrides: "
94      << overrides_.find(feature_name)->second.field_trial->trial_name()
95      << " / " << field_trial->trial_name();
96
97  RegisterOverride(feature_name, override_state, field_trial);
98}
99
100void FeatureList::GetFeatureOverrides(std::string* enable_overrides,
101                                      std::string* disable_overrides) {
102  DCHECK(initialized_);
103
104  enable_overrides->clear();
105  disable_overrides->clear();
106
107  // Note: Since |overrides_| is a std::map, iteration will be in alphabetical
108  // order. This not guaranteed to users of this function, but is useful for
109  // tests to assume the order.
110  for (const auto& entry : overrides_) {
111    std::string* target_list = nullptr;
112    switch (entry.second.overridden_state) {
113      case OVERRIDE_USE_DEFAULT:
114      case OVERRIDE_ENABLE_FEATURE:
115        target_list = enable_overrides;
116        break;
117      case OVERRIDE_DISABLE_FEATURE:
118        target_list = disable_overrides;
119        break;
120    }
121
122    if (!target_list->empty())
123      target_list->push_back(',');
124    if (entry.second.overridden_state == OVERRIDE_USE_DEFAULT)
125      target_list->push_back('*');
126    target_list->append(entry.first);
127    if (entry.second.field_trial) {
128      target_list->push_back('<');
129      target_list->append(entry.second.field_trial->trial_name());
130    }
131  }
132}
133
134// static
135bool FeatureList::IsEnabled(const Feature& feature) {
136  if (!g_instance) {
137    g_initialized_from_accessor = true;
138    return feature.default_state == FEATURE_ENABLED_BY_DEFAULT;
139  }
140  return g_instance->IsFeatureEnabled(feature);
141}
142
143// static
144FieldTrial* FeatureList::GetFieldTrial(const Feature& feature) {
145  return GetInstance()->GetAssociatedFieldTrial(feature);
146}
147
148// static
149std::vector<std::string> FeatureList::SplitFeatureListString(
150    const std::string& input) {
151  return SplitString(input, ",", TRIM_WHITESPACE, SPLIT_WANT_NONEMPTY);
152}
153
154// static
155bool FeatureList::InitializeInstance(const std::string& enable_features,
156                                     const std::string& disable_features) {
157  // We want to initialize a new instance here to support command-line features
158  // in testing better. For example, we initialize a dummy instance in
159  // base/test/test_suite.cc, and override it in content/browser/
160  // browser_main_loop.cc.
161  // On the other hand, we want to avoid re-initialization from command line.
162  // For example, we initialize an instance in chrome/browser/
163  // chrome_browser_main.cc and do not override it in content/browser/
164  // browser_main_loop.cc.
165  // If the singleton was previously initialized from within an accessor, we
166  // want to prevent callers from reinitializing the singleton and masking the
167  // accessor call(s) which likely returned incorrect information.
168  CHECK(!g_initialized_from_accessor);
169  bool instance_existed_before = false;
170  if (g_instance) {
171    if (g_instance->initialized_from_command_line_)
172      return false;
173
174    delete g_instance;
175    g_instance = nullptr;
176    instance_existed_before = true;
177  }
178
179  std::unique_ptr<base::FeatureList> feature_list(new base::FeatureList);
180  feature_list->InitializeFromCommandLine(enable_features, disable_features);
181  base::FeatureList::SetInstance(std::move(feature_list));
182  return !instance_existed_before;
183}
184
185// static
186FeatureList* FeatureList::GetInstance() {
187  return g_instance;
188}
189
190// static
191void FeatureList::SetInstance(std::unique_ptr<FeatureList> instance) {
192  DCHECK(!g_instance);
193  instance->FinalizeInitialization();
194
195  // Note: Intentional leak of global singleton.
196  g_instance = instance.release();
197}
198
199// static
200void FeatureList::ClearInstanceForTesting() {
201  delete g_instance;
202  g_instance = nullptr;
203  g_initialized_from_accessor = false;
204}
205
206void FeatureList::FinalizeInitialization() {
207  DCHECK(!initialized_);
208  initialized_ = true;
209}
210
211bool FeatureList::IsFeatureEnabled(const Feature& feature) {
212  DCHECK(initialized_);
213  DCHECK(IsValidFeatureOrFieldTrialName(feature.name)) << feature.name;
214  DCHECK(CheckFeatureIdentity(feature)) << feature.name;
215
216  auto it = overrides_.find(feature.name);
217  if (it != overrides_.end()) {
218    const OverrideEntry& entry = it->second;
219
220    // Activate the corresponding field trial, if necessary.
221    if (entry.field_trial)
222      entry.field_trial->group();
223
224    // TODO(asvitkine) Expand this section as more support is added.
225
226    // If marked as OVERRIDE_USE_DEFAULT, simply return the default state below.
227    if (entry.overridden_state != OVERRIDE_USE_DEFAULT)
228      return entry.overridden_state == OVERRIDE_ENABLE_FEATURE;
229  }
230  // Otherwise, return the default state.
231  return feature.default_state == FEATURE_ENABLED_BY_DEFAULT;
232}
233
234FieldTrial* FeatureList::GetAssociatedFieldTrial(const Feature& feature) {
235  DCHECK(initialized_);
236  DCHECK(IsValidFeatureOrFieldTrialName(feature.name)) << feature.name;
237  DCHECK(CheckFeatureIdentity(feature)) << feature.name;
238
239  auto it = overrides_.find(feature.name);
240  if (it != overrides_.end()) {
241    const OverrideEntry& entry = it->second;
242    return entry.field_trial;
243  }
244
245  return nullptr;
246}
247
248void FeatureList::RegisterOverridesFromCommandLine(
249    const std::string& feature_list,
250    OverrideState overridden_state) {
251  for (const auto& value : SplitFeatureListString(feature_list)) {
252    StringPiece feature_name(value);
253    base::FieldTrial* trial = nullptr;
254
255    // The entry may be of the form FeatureName<FieldTrialName - in which case,
256    // this splits off the field trial name and associates it with the override.
257    std::string::size_type pos = feature_name.find('<');
258    if (pos != std::string::npos) {
259      feature_name.set(value.data(), pos);
260      trial = base::FieldTrialList::Find(value.substr(pos + 1));
261    }
262
263    RegisterOverride(feature_name, overridden_state, trial);
264  }
265}
266
267void FeatureList::RegisterOverride(StringPiece feature_name,
268                                   OverrideState overridden_state,
269                                   FieldTrial* field_trial) {
270  DCHECK(!initialized_);
271  if (field_trial) {
272    DCHECK(IsValidFeatureOrFieldTrialName(field_trial->trial_name()))
273        << field_trial->trial_name();
274  }
275  if (feature_name.starts_with("*")) {
276    feature_name = feature_name.substr(1);
277    overridden_state = OVERRIDE_USE_DEFAULT;
278  }
279
280  // Note: The semantics of insert() is that it does not overwrite the entry if
281  // one already exists for the key. Thus, only the first override for a given
282  // feature name takes effect.
283  overrides_.insert(std::make_pair(
284      feature_name.as_string(), OverrideEntry(overridden_state, field_trial)));
285}
286
287bool FeatureList::CheckFeatureIdentity(const Feature& feature) {
288  AutoLock auto_lock(feature_identity_tracker_lock_);
289
290  auto it = feature_identity_tracker_.find(feature.name);
291  if (it == feature_identity_tracker_.end()) {
292    // If it's not tracked yet, register it.
293    feature_identity_tracker_[feature.name] = &feature;
294    return true;
295  }
296  // Compare address of |feature| to the existing tracked entry.
297  return it->second == &feature;
298}
299
300FeatureList::OverrideEntry::OverrideEntry(OverrideState overridden_state,
301                                          FieldTrial* field_trial)
302    : overridden_state(overridden_state),
303      field_trial(field_trial),
304      overridden_by_field_trial(field_trial != nullptr) {}
305
306}  // namespace base
307