• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 namespace base {
18 
19 namespace {
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.
24 FeatureList* g_instance = nullptr;
25 
26 // Tracks whether the FeatureList instance was initialized via an accessor.
27 bool 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.
IsValidFeatureOrFieldTrialName(const std::string & name)35 bool IsValidFeatureOrFieldTrialName(const std::string& name) {
36   return IsStringASCII(name) && name.find_first_of(",<*") == std::string::npos;
37 }
38 
39 }  // namespace
40 
FeatureList()41 FeatureList::FeatureList() {}
42 
~FeatureList()43 FeatureList::~FeatureList() {}
44 
InitializeFromCommandLine(const std::string & enable_features,const std::string & disable_features)45 void 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 
IsFeatureOverriddenFromCommandLine(const std::string & feature_name,OverrideState state) const58 bool 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 
AssociateReportingFieldTrial(const std::string & feature_name,OverrideState for_overridden_state,FieldTrial * field_trial)66 void 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 
RegisterFieldTrialOverride(const std::string & feature_name,OverrideState override_state,FieldTrial * field_trial)86 void 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 
GetFeatureOverrides(std::string * enable_overrides,std::string * disable_overrides)100 void 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
IsEnabled(const Feature & feature)135 bool 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
GetFieldTrial(const Feature & feature)144 FieldTrial* FeatureList::GetFieldTrial(const Feature& feature) {
145   return GetInstance()->GetAssociatedFieldTrial(feature);
146 }
147 
148 // static
SplitFeatureListString(const std::string & input)149 std::vector<std::string> FeatureList::SplitFeatureListString(
150     const std::string& input) {
151   return SplitString(input, ",", TRIM_WHITESPACE, SPLIT_WANT_NONEMPTY);
152 }
153 
154 // static
InitializeInstance(const std::string & enable_features,const std::string & disable_features)155 bool 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
GetInstance()186 FeatureList* FeatureList::GetInstance() {
187   return g_instance;
188 }
189 
190 // static
SetInstance(std::unique_ptr<FeatureList> instance)191 void 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
ClearInstanceForTesting()200 void FeatureList::ClearInstanceForTesting() {
201   delete g_instance;
202   g_instance = nullptr;
203   g_initialized_from_accessor = false;
204 }
205 
FinalizeInitialization()206 void FeatureList::FinalizeInitialization() {
207   DCHECK(!initialized_);
208   initialized_ = true;
209 }
210 
IsFeatureEnabled(const Feature & feature)211 bool 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 
GetAssociatedFieldTrial(const Feature & feature)234 FieldTrial* 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 
RegisterOverridesFromCommandLine(const std::string & feature_list,OverrideState overridden_state)248 void 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 
RegisterOverride(StringPiece feature_name,OverrideState overridden_state,FieldTrial * field_trial)267 void 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 
CheckFeatureIdentity(const Feature & feature)287 bool 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 
OverrideEntry(OverrideState overridden_state,FieldTrial * field_trial)300 FeatureList::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