-// Copyright 2015 The Chromium Authors. All rights reserved.
+// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/feature_list.h"
-#include <stddef.h>
+#include <string>
+#include <tuple>
-#include <utility>
-#include <vector>
+#include <stddef.h>
-#include "base/debug/alias.h"
+#include "base/base_switches.h"
+#include "base/debug/crash_logging.h"
+#include "base/debug/dump_without_crashing.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
+#include "base/memory/raw_ptr.h"
#include "base/metrics/field_trial.h"
+#include "base/metrics/field_trial_param_associator.h"
+#include "base/metrics/field_trial_params.h"
+#include "base/metrics/persistent_memory_allocator.h"
+#include "base/no_destructor.h"
+#include "base/notreached.h"
#include "base/pickle.h"
+#include "base/rand_util.h"
+#include "base/strings/string_piece.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
#include "build/build_config.h"
namespace base {
// have more control over initialization timing. Leaky.
FeatureList* g_feature_list_instance = nullptr;
-// Tracks whether the FeatureList instance was initialized via an accessor, and
-// which Feature that accessor was for, if so.
-const Feature* g_initialized_from_accessor = nullptr;
+// Tracks access to Feature state before FeatureList registration.
+class EarlyFeatureAccessTracker {
+ public:
+ static EarlyFeatureAccessTracker* GetInstance() {
+ static NoDestructor<EarlyFeatureAccessTracker> instance;
+ return instance.get();
+ }
+
+ // Invoked when `feature` is accessed before FeatureList registration.
+ void AccessedFeature(const Feature& feature) {
+ AutoLock lock(lock_);
+ if (fail_instantly_)
+ Fail(&feature);
+ else if (!feature_)
+ feature_ = &feature;
+ }
+
+ // Asserts that no feature was accessed before FeatureList registration.
+ void AssertNoAccess() {
+ AutoLock lock(lock_);
+ if (feature_)
+ Fail(feature_);
+ }
+
+ // Makes calls to AccessedFeature() fail instantly.
+ void FailOnFeatureAccessWithoutFeatureList() {
+ AutoLock lock(lock_);
+ if (feature_)
+ Fail(feature_);
+ fail_instantly_ = true;
+ }
+
+ // Resets the state of this tracker.
+ void Reset() {
+ AutoLock lock(lock_);
+ feature_ = nullptr;
+ fail_instantly_ = false;
+ }
+
+ private:
+ void Fail(const Feature* feature) {
+ // TODO(crbug.com/1358639): Enable this check on all platforms.
+#if !BUILDFLAG(IS_IOS) && !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_CHROMEOS)
+#if !BUILDFLAG(IS_NACL)
+ // Create a crash key with the name of the feature accessed too early, to
+ // facilitate crash triage.
+ SCOPED_CRASH_KEY_STRING256("FeatureList", "feature-accessed-too-early",
+ feature->name);
+#endif // !BUILDFLAG(IS_NACL)
+ CHECK(!feature) << "Accessed feature " << feature->name
+ << " before FeatureList registration.";
+#endif // !BUILDFLAG(IS_IOS) && !BUILDFLAG(IS_ANDROID) &&
+ // !BUILDFLAG(IS_CHROMEOS)
+ }
+
+ friend class NoDestructor<EarlyFeatureAccessTracker>;
+
+ EarlyFeatureAccessTracker() = default;
+ ~EarlyFeatureAccessTracker() = default;
+
+ Lock lock_;
+
+ // First feature to be accessed before FeatureList registration.
+ raw_ptr<const Feature> feature_ GUARDED_BY(lock_) = nullptr;
+
+ // Whether AccessedFeature() should fail instantly.
+ bool fail_instantly_ GUARDED_BY(lock_) = false;
+};
#if DCHECK_IS_ON()
const char* g_reason_overrides_disallowed = nullptr;
// followed by a base::Pickle object that contains the feature and trial name.
struct FeatureEntry {
// SHA1(FeatureEntry): Increment this if structure changes!
- static constexpr uint32_t kPersistentTypeId = 0x06567CA6 + 1;
+ static constexpr uint32_t kPersistentTypeId = 0x06567CA6 + 2;
// Expected size for 32/64-bit check.
- static constexpr size_t kExpectedInstanceSize = 8;
+ static constexpr size_t kExpectedInstanceSize = 16;
// Specifies whether a feature override enables or disables the feature. Same
// values as the OverrideState enum in feature_list.h
uint32_t override_state;
+ // On e.g. x86, alignof(uint64_t) is 4. Ensure consistent size and alignment
+ // of `pickle_size` across platforms.
+ uint32_t padding;
+
// Size of the pickled structure, NOT the total size of this entry.
- uint32_t pickle_size;
+ uint64_t pickle_size;
// Reads the feature and trial name from the pickle. Calling this is only
// valid on an initialized entry that's in shared memory.
const char* src =
reinterpret_cast<const char*>(this) + sizeof(FeatureEntry);
- Pickle pickle(src, pickle_size);
+ Pickle pickle(src, checked_cast<size_t>(pickle_size));
PickleIterator pickle_iter(pickle);
if (!pickle_iter.ReadStringPiece(feature_name))
return false;
// Return true because we are not guaranteed to have a trial name anyways.
- auto sink = pickle_iter.ReadStringPiece(trial_name);
- ALLOW_UNUSED_LOCAL(sink);
+ std::ignore = pickle_iter.ReadStringPiece(trial_name);
return true;
}
};
-// Some characters are not allowed to appear in feature names or the associated
-// field trial names, as they are used as special characters for command-line
-// serialization. This function checks that the strings are ASCII (since they
-// are used in command-line API functions that require ASCII) and whether there
-// are any reserved characters present, returning true if the string is valid.
-// Only called in DCHECKs.
-bool IsValidFeatureOrFieldTrialName(const std::string& name) {
- return IsStringASCII(name) && name.find_first_of(",<*") == std::string::npos;
+// Splits |text| into two parts by the |separator| where the first part will be
+// returned updated in |first| and the second part will be returned as |second|.
+// This function returns false if there is more than one |separator| in |first|.
+// If there is no |separator| presented in |first|, this function will not
+// modify |first| and |second|. It's used for splitting the |enable_features|
+// flag into feature name, field trial name and feature parameters.
+bool SplitIntoTwo(StringPiece text,
+ StringPiece separator,
+ StringPiece* first,
+ std::string* second) {
+ std::vector<StringPiece> parts =
+ SplitStringPiece(text, separator, TRIM_WHITESPACE, SPLIT_WANT_ALL);
+ if (parts.size() == 2) {
+ *second = std::string(parts[1]);
+ } else if (parts.size() > 2) {
+ DLOG(ERROR) << "Only one '" << separator
+ << "' is allowed but got: " << *first;
+ return false;
+ }
+ *first = parts[0];
+ return true;
+}
+
+// Checks and parses the |enable_features| flag and sets
+// |parsed_enable_features| to be a comma-separated list of features,
+// |force_fieldtrials| to be a comma-separated list of field trials that each
+// feature want to associate with and |force_fieldtrial_params| to be the field
+// trial parameters for each field trial.
+// Returns true if |enable_features| is parsable, otherwise false.
+bool ParseEnableFeatures(const std::string& enable_features,
+ std::string* parsed_enable_features,
+ std::string* force_fieldtrials,
+ std::string* force_fieldtrial_params) {
+ std::vector<std::string> enable_features_list;
+ std::vector<std::string> force_fieldtrials_list;
+ std::vector<std::string> force_fieldtrial_params_list;
+ for (const auto& enable_feature :
+ FeatureList::SplitFeatureListString(enable_features)) {
+ std::string feature_name;
+ std::string study;
+ std::string group;
+ std::string feature_params;
+ if (!FeatureList::ParseEnableFeatureString(
+ enable_feature, &feature_name, &study, &group, &feature_params)) {
+ return false;
+ }
+
+ // If feature params were set but group and study weren't, associate the
+ // feature and its feature params to a synthetic field trial as the
+ // feature params only make sense when it's combined with a field trial.
+ if (!feature_params.empty()) {
+ force_fieldtrials_list.push_back(study + "/" + group);
+ force_fieldtrial_params_list.push_back(study + "." + group + ":" +
+ feature_params);
+ }
+ enable_features_list.push_back(
+ study.empty() ? feature_name : (feature_name + "<" + study));
+ }
+
+ *parsed_enable_features = JoinString(enable_features_list, ",");
+ // Field trial separator is currently a slash. See
+ // |kPersistentStringSeparator| in base/metrics/field_trial.cc.
+ *force_fieldtrials = JoinString(force_fieldtrials_list, "/");
+ *force_fieldtrial_params = JoinString(force_fieldtrial_params_list, ",");
+ return true;
+}
+
+std::pair<FeatureList::OverrideState, uint16_t> UnpackFeatureCache(
+ uint32_t packed_cache_value) {
+ return std::make_pair(
+ static_cast<FeatureList::OverrideState>(packed_cache_value >> 24),
+ packed_cache_value & 0xFFFF);
+}
+
+uint32_t PackFeatureCache(FeatureList::OverrideState override_state,
+ uint32_t caching_context) {
+ return (static_cast<uint32_t>(override_state) << 24) |
+ (caching_context & 0xFFFF);
}
} // namespace
-#if defined(DCHECK_IS_CONFIGURABLE)
-const Feature kDCheckIsFatalFeature{"DcheckIsFatal",
- FEATURE_DISABLED_BY_DEFAULT};
-#endif // defined(DCHECK_IS_CONFIGURABLE)
+#if BUILDFLAG(DCHECK_IS_CONFIGURABLE)
+BASE_FEATURE(kDCheckIsFatalFeature,
+ "DcheckIsFatal",
+ FEATURE_DISABLED_BY_DEFAULT);
+#endif // BUILDFLAG(DCHECK_IS_CONFIGURABLE)
FeatureList::FeatureList() = default;
const std::string& disable_features) {
DCHECK(!initialized_);
+ std::string parsed_enable_features;
+ std::string force_fieldtrials;
+ std::string force_fieldtrial_params;
+ bool parse_enable_features_result =
+ ParseEnableFeatures(enable_features, &parsed_enable_features,
+ &force_fieldtrials, &force_fieldtrial_params);
+ DCHECK(parse_enable_features_result) << StringPrintf(
+ "The --%s list is unparsable or invalid, please check the format.",
+ ::switches::kEnableFeatures);
+
+ // Only create field trials when field_trial_list is available. Some tests
+ // don't have field trial list available.
+ if (FieldTrialList::GetInstance()) {
+ bool associate_params_result = AssociateFieldTrialParamsFromString(
+ force_fieldtrial_params, &UnescapeValue);
+ DCHECK(associate_params_result) << StringPrintf(
+ "The field trial parameters part of the --%s list is invalid. Make "
+ "sure "
+ "you %%-encode the following characters in param values: %%:/.,",
+ ::switches::kEnableFeatures);
+
+ bool create_trials_result =
+ FieldTrialList::CreateTrialsFromString(force_fieldtrials);
+ DCHECK(create_trials_result)
+ << StringPrintf("Invalid field trials are specified in --%s.",
+ ::switches::kEnableFeatures);
+ }
+
// Process disabled features first, so that disabled ones take precedence over
// enabled ones (since RegisterOverride() uses insert()).
RegisterOverridesFromCommandLine(disable_features, OVERRIDE_DISABLE_FEATURE);
- RegisterOverridesFromCommandLine(enable_features, OVERRIDE_ENABLE_FEATURE);
+ RegisterOverridesFromCommandLine(parsed_enable_features,
+ OVERRIDE_ENABLE_FEATURE);
initialized_from_command_line_ = true;
}
if (!entry->GetFeatureAndTrialName(&feature_name, &trial_name))
continue;
- FieldTrial* trial = FieldTrialList::Find(trial_name.as_string());
+ FieldTrial* trial = FieldTrialList::Find(trial_name);
RegisterOverride(feature_name, override_state, trial);
}
}
+bool FeatureList::IsFeatureOverridden(const std::string& feature_name) const {
+ return overrides_.count(feature_name);
+}
+
+bool FeatureList::IsFeatureOverriddenFromCommandLine(
+ const std::string& feature_name) const {
+ auto it = overrides_.find(feature_name);
+ return it != overrides_.end() && !it->second.overridden_by_field_trial;
+}
+
bool FeatureList::IsFeatureOverriddenFromCommandLine(
const std::string& feature_name,
OverrideState state) const {
auto it = overrides_.find(feature_name);
- return it != overrides_.end() && it->second.overridden_state == state &&
- !it->second.overridden_by_field_trial;
+ return it != overrides_.end() && !it->second.overridden_by_field_trial &&
+ it->second.overridden_state == state;
}
void FeatureList::AssociateReportingFieldTrial(
OverrideState override_state,
FieldTrial* field_trial) {
DCHECK(field_trial);
- DCHECK(!Contains(overrides_, feature_name) ||
- !overrides_.find(feature_name)->second.field_trial)
- << "Feature " << feature_name
- << " has conflicting field trial overrides: "
+ DCHECK(!HasAssociatedFieldTrialByFeatureName(feature_name))
+ << "Feature " << feature_name << " is overriden multiple times in these "
+ << "trials: "
<< overrides_.find(feature_name)->second.field_trial->trial_name()
- << " / " << field_trial->trial_name()
- << ". Please make sure that the trial (study) name is consistent across:"
- << " (1)The server config, (2)The fieldtrial_testing_config, and"
- << " (3) The about_flags.cc";
+ << " and " << field_trial->trial_name() << ". "
+ << "Check the trial (study) in (1) the server config, "
+ << "(2) fieldtrial_testing_config.json, (3) about_flags.cc, and "
+ << "(4) client-side field trials.";
RegisterOverride(feature_name, override_state, field_trial);
}
}
void FeatureList::GetFeatureOverrides(std::string* enable_overrides,
- std::string* disable_overrides) {
- GetFeatureOverridesImpl(enable_overrides, disable_overrides, false);
+ std::string* disable_overrides,
+ bool include_group_name) const {
+ GetFeatureOverridesImpl(enable_overrides, disable_overrides, false,
+ include_group_name);
}
void FeatureList::GetCommandLineFeatureOverrides(
std::string* enable_overrides,
- std::string* disable_overrides) {
+ std::string* disable_overrides) const {
GetFeatureOverridesImpl(enable_overrides, disable_overrides, true);
}
// static
bool FeatureList::IsEnabled(const Feature& feature) {
if (!g_feature_list_instance) {
- g_initialized_from_accessor = &feature;
+ EarlyFeatureAccessTracker::GetInstance()->AccessedFeature(feature);
return feature.default_state == FEATURE_ENABLED_BY_DEFAULT;
}
return g_feature_list_instance->IsFeatureEnabled(feature);
}
// static
+bool FeatureList::IsValidFeatureOrFieldTrialName(StringPiece name) {
+ return IsStringASCII(name) && name.find_first_of(",<*") == std::string::npos;
+}
+
+// static
+absl::optional<bool> FeatureList::GetStateIfOverridden(const Feature& feature) {
+ if (!g_feature_list_instance) {
+ EarlyFeatureAccessTracker::GetInstance()->AccessedFeature(feature);
+ // If there is no feature list, there can be no overrides.
+ return absl::nullopt;
+ }
+ return g_feature_list_instance->IsFeatureEnabledIfOverridden(feature);
+}
+
+// static
FieldTrial* FeatureList::GetFieldTrial(const Feature& feature) {
if (!g_feature_list_instance) {
- g_initialized_from_accessor = &feature;
+ EarlyFeatureAccessTracker::GetInstance()->AccessedFeature(feature);
return nullptr;
}
return g_feature_list_instance->GetAssociatedFieldTrial(feature);
}
// static
+bool FeatureList::ParseEnableFeatureString(StringPiece enable_feature,
+ std::string* feature_name,
+ std::string* study_name,
+ std::string* group_name,
+ std::string* params) {
+ StringPiece first;
+ // First, check whether ":" is present. If true, feature parameters were
+ // set for this feature.
+ std::string feature_params;
+ if (!SplitIntoTwo(enable_feature, ":", &first, &feature_params))
+ return false;
+ // Then, check whether "." is present. If true, a group was specified for
+ // this feature.
+ std::string group;
+ if (!SplitIntoTwo(first, ".", &first, &group))
+ return false;
+ // Finally, check whether "<" is present. If true, a study was specified for
+ // this feature.
+ std::string study;
+ if (!SplitIntoTwo(first, "<", &first, &study))
+ return false;
+
+ std::string enable_feature_name(first);
+ // If feature params were set but group and study weren't, associate the
+ // feature and its feature params to a synthetic field trial as the
+ // feature params only make sense when it's combined with a field trial.
+ if (!feature_params.empty()) {
+ study = study.empty() ? "Study" + enable_feature_name : study;
+ group = group.empty() ? "Group" + enable_feature_name : group;
+ }
+
+ feature_name->swap(enable_feature_name);
+ study_name->swap(study);
+ group_name->swap(group);
+ params->swap(feature_params);
+ return true;
+}
+
+// static
bool FeatureList::InitializeInstance(const std::string& enable_features,
const std::string& disable_features) {
return InitializeInstance(enable_features, disable_features,
// If the singleton was previously initialized from within an accessor, we
// want to prevent callers from reinitializing the singleton and masking the
// accessor call(s) which likely returned incorrect information.
- if (g_initialized_from_accessor) {
- DEBUG_ALIAS_FOR_CSTR(accessor_name, g_initialized_from_accessor->name, 128);
- CHECK(!g_initialized_from_accessor);
- }
+ EarlyFeatureAccessTracker::GetInstance()->AssertNoAccess();
bool instance_existed_before = false;
if (g_feature_list_instance) {
if (g_feature_list_instance->initialized_from_command_line_)
// Note: Intentional leak of global singleton.
g_feature_list_instance = instance.release();
-#if defined(DCHECK_IS_CONFIGURABLE)
- // Update the behaviour of LOG_DCHECK to match the Feature configuration.
+ EarlyFeatureAccessTracker::GetInstance()->AssertNoAccess();
+
+#if !BUILDFLAG(IS_NACL)
+ // Configured first because it takes precedence over the getrandom() trial.
+ internal::ConfigureBoringSSLBackedRandBytesFieldTrial();
+#endif
+
+#if BUILDFLAG(IS_ANDROID)
+ internal::ConfigureRandBytesFieldTrial();
+#endif
+
+#if BUILDFLAG(DCHECK_IS_CONFIGURABLE)
+ // Update the behaviour of LOGGING_DCHECK to match the Feature configuration.
// DCHECK is also forced to be FATAL if we are running a death-test.
// TODO(crbug.com/1057995#c11): --gtest_internal_run_death_test doesn't
// currently run through this codepath, mitigated in
if (FeatureList::IsEnabled(kDCheckIsFatalFeature) ||
CommandLine::ForCurrentProcess()->HasSwitch(
"gtest_internal_run_death_test")) {
- logging::LOG_DCHECK = logging::LOG_FATAL;
+ logging::LOGGING_DCHECK = logging::LOG_FATAL;
} else {
- logging::LOG_DCHECK = logging::LOG_INFO;
+ logging::LOGGING_DCHECK = logging::LOG_INFO;
}
-#endif // defined(DCHECK_IS_CONFIGURABLE)
+#endif // BUILDFLAG(DCHECK_IS_CONFIGURABLE)
}
// static
std::unique_ptr<FeatureList> FeatureList::ClearInstanceForTesting() {
FeatureList* old_instance = g_feature_list_instance;
g_feature_list_instance = nullptr;
- g_initialized_from_accessor = nullptr;
+ EarlyFeatureAccessTracker::GetInstance()->Reset();
return WrapUnique(old_instance);
}
g_feature_list_instance = instance.release();
}
+// static
+void FeatureList::FailOnFeatureAccessWithoutFeatureList() {
+ EarlyFeatureAccessTracker::GetInstance()
+ ->FailOnFeatureAccessWithoutFeatureList();
+}
+
+void FeatureList::SetCachingContextForTesting(uint16_t caching_context) {
+ caching_context_ = caching_context;
+}
+
void FeatureList::FinalizeInitialization() {
DCHECK(!initialized_);
// Store the field trial list pointer for DCHECKing.
initialized_ = true;
}
-bool FeatureList::IsFeatureEnabled(const Feature& feature) {
+bool FeatureList::IsFeatureEnabled(const Feature& feature) const {
+ OverrideState overridden_state = GetOverrideState(feature);
+
+ // If marked as OVERRIDE_USE_DEFAULT, simply return the default state below.
+ if (overridden_state != OVERRIDE_USE_DEFAULT)
+ return overridden_state == OVERRIDE_ENABLE_FEATURE;
+
+ return feature.default_state == FEATURE_ENABLED_BY_DEFAULT;
+}
+
+absl::optional<bool> FeatureList::IsFeatureEnabledIfOverridden(
+ const Feature& feature) const {
+ OverrideState overridden_state = GetOverrideState(feature);
+
+ // If marked as OVERRIDE_USE_DEFAULT, fall through to returning empty.
+ if (overridden_state != OVERRIDE_USE_DEFAULT)
+ return overridden_state == OVERRIDE_ENABLE_FEATURE;
+
+ return absl::nullopt;
+}
+
+FeatureList::OverrideState FeatureList::GetOverrideState(
+ const Feature& feature) const {
DCHECK(initialized_);
DCHECK(IsValidFeatureOrFieldTrialName(feature.name)) << feature.name;
- DCHECK(CheckFeatureIdentity(feature)) << feature.name;
+ DCHECK(CheckFeatureIdentity(feature))
+ << feature.name
+ << " has multiple definitions. Either it is defined more than once in "
+ "code or (for component builds) the code is built into multiple "
+ "components (shared libraries) without a corresponding export "
+ "statement";
+
+ uint32_t current_cache_value =
+ feature.cached_value.load(std::memory_order_relaxed);
+
+ auto unpacked = UnpackFeatureCache(current_cache_value);
+
+ if (unpacked.second == caching_context_)
+ return unpacked.first;
+
+ OverrideState state = GetOverrideStateByFeatureName(feature.name);
+ uint32_t new_cache_value = PackFeatureCache(state, caching_context_);
+
+ // Update the cache with the new value.
+ // In non-test code, this value can be in one of 2 states: either it's unset,
+ // or another thread has updated it to the same value we're about to write.
+ // Because of this, a plain `store` yields the correct result in all cases.
+ // In test code, it's possible for a different thread to have installed a new
+ // `ScopedFeatureList` and written a value that's different than the one we're
+ // about to write, although that would be a thread safety violation already
+ // and such tests should be fixed.
+ feature.cached_value.store(new_cache_value, std::memory_order_relaxed);
+
+ return state;
+}
+
+FeatureList::OverrideState FeatureList::GetOverrideStateByFeatureName(
+ StringPiece feature_name) const {
+ DCHECK(initialized_);
+ DCHECK(IsValidFeatureOrFieldTrialName(feature_name)) << feature_name;
- auto it = overrides_.find(feature.name);
+ auto it = overrides_.find(feature_name);
if (it != overrides_.end()) {
const OverrideEntry& entry = it->second;
// Activate the corresponding field trial, if necessary.
if (entry.field_trial)
- entry.field_trial->group();
+ entry.field_trial->Activate();
// TODO(asvitkine) Expand this section as more support is added.
- // If marked as OVERRIDE_USE_DEFAULT, simply return the default state below.
- if (entry.overridden_state != OVERRIDE_USE_DEFAULT)
- return entry.overridden_state == OVERRIDE_ENABLE_FEATURE;
+ return entry.overridden_state;
}
- // Otherwise, return the default state.
- return feature.default_state == FEATURE_ENABLED_BY_DEFAULT;
+ // Otherwise, report that we want to use the default state.
+ return OVERRIDE_USE_DEFAULT;
}
-FieldTrial* FeatureList::GetAssociatedFieldTrial(const Feature& feature) {
+FieldTrial* FeatureList::GetAssociatedFieldTrial(const Feature& feature) const {
DCHECK(initialized_);
- DCHECK(IsValidFeatureOrFieldTrialName(feature.name)) << feature.name;
DCHECK(CheckFeatureIdentity(feature)) << feature.name;
- auto it = overrides_.find(feature.name);
+ return GetAssociatedFieldTrialByFeatureName(feature.name);
+}
+
+const base::FeatureList::OverrideEntry*
+FeatureList::GetOverrideEntryByFeatureName(StringPiece name) const {
+ DCHECK(initialized_);
+ DCHECK(IsValidFeatureOrFieldTrialName(name)) << name;
+
+ auto it = overrides_.find(name);
if (it != overrides_.end()) {
const OverrideEntry& entry = it->second;
- return entry.field_trial;
+ return &entry;
}
+ return nullptr;
+}
+
+FieldTrial* FeatureList::GetAssociatedFieldTrialByFeatureName(
+ StringPiece name) const {
+ DCHECK(initialized_);
+ const base::FeatureList::OverrideEntry* entry =
+ GetOverrideEntryByFeatureName(name);
+ if (entry) {
+ return entry->field_trial;
+ }
return nullptr;
}
+bool FeatureList::HasAssociatedFieldTrialByFeatureName(StringPiece name) const {
+ DCHECK(!initialized_);
+ auto entry = overrides_.find(name);
+ return entry != overrides_.end() && entry->second.field_trial != nullptr;
+}
+
+FieldTrial* FeatureList::GetEnabledFieldTrialByFeatureName(
+ StringPiece name) const {
+ DCHECK(initialized_);
+
+ const base::FeatureList::OverrideEntry* entry =
+ GetOverrideEntryByFeatureName(name);
+ if (entry &&
+ entry->overridden_state == base::FeatureList::OVERRIDE_ENABLE_FEATURE) {
+ return entry->field_trial;
+ }
+ return nullptr;
+}
+
+std::unique_ptr<FeatureList::Accessor> FeatureList::ConstructAccessor() {
+ if (initialized_) {
+ // This function shouldn't be called after initialization.
+ NOTREACHED();
+ return nullptr;
+ }
+ // Use new and WrapUnique because we want to restrict access to the Accessor's
+ // constructor.
+ return base::WrapUnique(new Accessor(this));
+}
+
void FeatureList::RegisterOverridesFromCommandLine(
const std::string& feature_list,
OverrideState overridden_state) {
std::string::size_type pos = feature_name.find('<');
if (pos != std::string::npos) {
feature_name = StringPiece(value.data(), pos);
- trial = FieldTrialList::Find(value.substr(pos + 1).as_string());
-#if !defined(OS_NACL)
+ trial = FieldTrialList::Find(value.substr(pos + 1));
+#if !BUILDFLAG(IS_NACL)
// If the below DCHECK fires, it means a non-existent trial name was
// specified via the "Feature<Trial" command-line syntax.
- DCHECK(trial) << "trial=" << value.substr(pos + 1);
-#endif // !defined(OS_NACL)
+ DCHECK(trial) << "trial='" << value.substr(pos + 1) << "' does not exist";
+#endif // !BUILDFLAG(IS_NACL)
}
RegisterOverride(feature_name, overridden_state, trial);
DCHECK(IsValidFeatureOrFieldTrialName(field_trial->trial_name()))
<< field_trial->trial_name();
}
- if (feature_name.starts_with("*")) {
+ if (StartsWith(feature_name, "*")) {
feature_name = feature_name.substr(1);
overridden_state = OVERRIDE_USE_DEFAULT;
}
- // Note: The semantics of insert() is that it does not overwrite the entry if
+ // Note: The semantics of emplace() is that it does not overwrite the entry if
// one already exists for the key. Thus, only the first override for a given
// feature name takes effect.
- overrides_.insert(std::make_pair(
- feature_name.as_string(), OverrideEntry(overridden_state, field_trial)));
+ overrides_.emplace(std::string(feature_name),
+ OverrideEntry(overridden_state, field_trial));
}
void FeatureList::GetFeatureOverridesImpl(std::string* enable_overrides,
std::string* disable_overrides,
- bool command_line_only) {
+ bool command_line_only,
+ bool include_group_name) const {
DCHECK(initialized_);
// Check that the FieldTrialList this is associated with, if any, is the
target_list->push_back('*');
target_list->append(entry.first);
if (entry.second.field_trial) {
+ auto* const field_trial = entry.second.field_trial.get();
target_list->push_back('<');
- target_list->append(entry.second.field_trial->trial_name());
+ target_list->append(field_trial->trial_name());
+ if (include_group_name) {
+ target_list->push_back('.');
+ target_list->append(field_trial->GetGroupNameWithoutActivation());
+ }
}
}
}
-bool FeatureList::CheckFeatureIdentity(const Feature& feature) {
+bool FeatureList::CheckFeatureIdentity(const Feature& feature) const {
AutoLock auto_lock(feature_identity_tracker_lock_);
auto it = feature_identity_tracker_.find(feature.name);
field_trial(field_trial),
overridden_by_field_trial(field_trial != nullptr) {}
+FeatureList::Accessor::Accessor(FeatureList* feature_list)
+ : feature_list_(feature_list) {}
+
+FeatureList::OverrideState FeatureList::Accessor::GetOverrideStateByFeatureName(
+ StringPiece feature_name) {
+ return feature_list_->GetOverrideStateByFeatureName(feature_name);
+}
+
+bool FeatureList::Accessor::GetParamsByFeatureName(
+ StringPiece feature_name,
+ std::map<std::string, std::string>* params) {
+ base::FieldTrial* trial =
+ feature_list_->GetAssociatedFieldTrialByFeatureName(feature_name);
+ return FieldTrialParamAssociator::GetInstance()->GetFieldTrialParams(trial,
+ params);
+}
+
} // namespace base