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