blob: c08d3983e754ac44a504dd5f07032509bd7d249d [file] [log] [blame]
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/flags_ui/flags_state.h"
#include <memory>
#include <utility>
#include "base/callback.h"
#include "base/feature_list.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/field_trial.h"
#include "base/stl_util.h"
#include "base/strings/string_piece.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
#include "build/build_config.h"
#include "components/flags_ui/feature_entry.h"
#include "components/flags_ui/flags_storage.h"
#include "components/flags_ui/flags_ui_switches.h"
#include "components/variations/variations_associated_data.h"
#include "ui/base/l10n/l10n_util.h"
namespace flags_ui {
namespace internal {
const char kTrialGroupAboutFlags[] = "AboutFlags";
} // namespace internal
namespace {
// Convert switch constants to proper CommandLine::StringType strings.
base::CommandLine::StringType GetSwitchString(const std::string& flag) {
base::CommandLine cmd_line(base::CommandLine::NO_PROGRAM);
cmd_line.AppendSwitch(flag);
DCHECK_EQ(2U, cmd_line.argv().size());
return cmd_line.argv()[1];
}
// Scoops flags from a command line.
// Only switches between --flag-switches-begin and --flag-switches-end are
// compared. The embedder may use |extra_flag_sentinel_begin_flag_name| and
// |extra_sentinel_end_flag_name| to specify other delimiters, if supported.
std::set<base::CommandLine::StringType> ExtractFlagsFromCommandLine(
const base::CommandLine& cmdline,
const char* extra_flag_sentinel_begin_flag_name,
const char* extra_flag_sentinel_end_flag_name) {
DCHECK_EQ(!!extra_flag_sentinel_begin_flag_name,
!!extra_flag_sentinel_end_flag_name);
std::set<base::CommandLine::StringType> flags;
// First do the ones between --flag-switches-begin and --flag-switches-end.
base::CommandLine::StringVector::const_iterator first =
std::find(cmdline.argv().begin(), cmdline.argv().end(),
GetSwitchString(switches::kFlagSwitchesBegin));
base::CommandLine::StringVector::const_iterator last =
std::find(cmdline.argv().begin(), cmdline.argv().end(),
GetSwitchString(switches::kFlagSwitchesEnd));
if (first != cmdline.argv().end() && last != cmdline.argv().end())
flags.insert(first + 1, last);
// Then add those between the extra sentinels.
if (extra_flag_sentinel_begin_flag_name &&
extra_flag_sentinel_end_flag_name) {
first = std::find(cmdline.argv().begin(), cmdline.argv().end(),
GetSwitchString(extra_flag_sentinel_begin_flag_name));
last = std::find(cmdline.argv().begin(), cmdline.argv().end(),
GetSwitchString(extra_flag_sentinel_end_flag_name));
if (first != cmdline.argv().end() && last != cmdline.argv().end())
flags.insert(first + 1, last);
}
return flags;
}
const struct {
unsigned bit;
const char* const name;
} kBitsToOs[] = {
{kOsMac, "Mac"},
{kOsWin, "Windows"},
{kOsLinux, "Linux"},
{kOsCrOS, "Chrome OS"},
{kOsAndroid, "Android"},
{kOsCrOSOwnerOnly, "Chrome OS (owner only)"},
{kOsIos, "iOS"},
};
// Adds a |StringValue| to |list| for each platform where |bitmask| indicates
// whether the entry is available on that platform.
void AddOsStrings(unsigned bitmask, base::ListValue* list) {
for (size_t i = 0; i < arraysize(kBitsToOs); ++i) {
if (bitmask & kBitsToOs[i].bit)
list->AppendString(kBitsToOs[i].name);
}
}
// Adds the internal names for the specified entry to |names|.
void AddInternalName(const FeatureEntry& e, std::set<std::string>* names) {
switch (e.type) {
case FeatureEntry::SINGLE_VALUE:
case FeatureEntry::SINGLE_DISABLE_VALUE:
names->insert(e.internal_name);
break;
case FeatureEntry::MULTI_VALUE:
case FeatureEntry::ENABLE_DISABLE_VALUE:
case FeatureEntry::FEATURE_VALUE:
case FeatureEntry::FEATURE_WITH_PARAMS_VALUE:
for (int i = 0; i < e.num_options; ++i)
names->insert(e.NameForOption(i));
break;
}
}
// Confirms that an entry is valid, used in a DCHECK in
// SanitizeList below.
bool ValidateFeatureEntry(const FeatureEntry& e) {
switch (e.type) {
case FeatureEntry::SINGLE_VALUE:
case FeatureEntry::SINGLE_DISABLE_VALUE:
DCHECK_EQ(0, e.num_options);
DCHECK(!e.choices);
return true;
case FeatureEntry::MULTI_VALUE:
DCHECK_GT(e.num_options, 0);
DCHECK(e.choices);
DCHECK(e.ChoiceForOption(0).command_line_switch);
DCHECK_EQ('\0', e.ChoiceForOption(0).command_line_switch[0]);
return true;
case FeatureEntry::ENABLE_DISABLE_VALUE:
DCHECK_EQ(3, e.num_options);
DCHECK(!e.choices);
DCHECK(e.command_line_switch);
DCHECK(e.command_line_value);
DCHECK(e.disable_command_line_switch);
DCHECK(e.disable_command_line_value);
return true;
case FeatureEntry::FEATURE_VALUE:
DCHECK_EQ(3, e.num_options);
DCHECK(!e.choices);
DCHECK(e.feature);
return true;
case FeatureEntry::FEATURE_WITH_PARAMS_VALUE:
DCHECK_GT(e.num_options, 2);
DCHECK(!e.choices);
DCHECK(e.feature);
DCHECK(e.feature_variations);
DCHECK(e.feature_trial_name);
return true;
}
NOTREACHED();
return false;
}
// Returns true if none of this entry's options have been enabled.
bool IsDefaultValue(const FeatureEntry& entry,
const std::set<std::string>& enabled_entries) {
switch (entry.type) {
case FeatureEntry::SINGLE_VALUE:
case FeatureEntry::SINGLE_DISABLE_VALUE:
return enabled_entries.count(entry.internal_name) == 0;
case FeatureEntry::MULTI_VALUE:
case FeatureEntry::ENABLE_DISABLE_VALUE:
case FeatureEntry::FEATURE_VALUE:
case FeatureEntry::FEATURE_WITH_PARAMS_VALUE:
for (int i = 0; i < entry.num_options; ++i) {
if (enabled_entries.count(entry.NameForOption(i)) > 0)
return false;
}
return true;
}
NOTREACHED();
return true;
}
// Returns the Value representing the choice data in the specified entry.
std::unique_ptr<base::Value> CreateOptionsData(
const FeatureEntry& entry,
const std::set<std::string>& enabled_entries) {
DCHECK(entry.type == FeatureEntry::MULTI_VALUE ||
entry.type == FeatureEntry::ENABLE_DISABLE_VALUE ||
entry.type == FeatureEntry::FEATURE_VALUE ||
entry.type == FeatureEntry::FEATURE_WITH_PARAMS_VALUE);
auto result = base::MakeUnique<base::ListValue>();
for (int i = 0; i < entry.num_options; ++i) {
auto value = base::MakeUnique<base::DictionaryValue>();
const std::string name = entry.NameForOption(i);
value->SetString("internal_name", name);
value->SetString("description", entry.DescriptionForOption(i));
value->SetBoolean("selected", enabled_entries.count(name) > 0);
result->Append(std::move(value));
}
return std::move(result);
}
// Registers variation parameters specified by |feature_variation_params| for
// the field trial named |feature_trial_name|, unless a group for this trial has
// already been created (e.g. via command-line switches that take precedence
// over about:flags). In the trial, the function creates a new constant group
// called |kTrialGroupAboutFlags|.
base::FieldTrial* RegisterFeatureVariationParameters(
const std::string& feature_trial_name,
const std::map<std::string, std::string>& feature_variation_params) {
bool success = variations::AssociateVariationParams(
feature_trial_name, internal::kTrialGroupAboutFlags,
feature_variation_params);
if (!success)
return nullptr;
// Successful association also means that no group is created and selected
// for the trial, yet. Thus, create the trial to select the group. This way,
// the parameters cannot get overwritten in later phases (such as from the
// server).
base::FieldTrial* trial = base::FieldTrialList::CreateFieldTrial(
feature_trial_name, internal::kTrialGroupAboutFlags);
if (!trial) {
DLOG(WARNING) << "Could not create the trial " << feature_trial_name
<< " with group " << internal::kTrialGroupAboutFlags;
}
return trial;
}
} // namespace
struct FlagsState::SwitchEntry {
// Corresponding base::Feature to toggle.
std::string feature_name;
// If |feature_name| is not empty, the state (enable/disabled) to set.
bool feature_state;
// The name of the switch to add.
std::string switch_name;
// If |switch_name| is not empty, the value of the switch to set.
std::string switch_value;
SwitchEntry() : feature_state(false) {}
};
FlagsState::FlagsState(const FeatureEntry* feature_entries,
size_t num_feature_entries)
: feature_entries_(feature_entries),
num_feature_entries_(num_feature_entries),
needs_restart_(false) {}
FlagsState::~FlagsState() {}
void FlagsState::ConvertFlagsToSwitches(
FlagsStorage* flags_storage,
base::CommandLine* command_line,
SentinelsMode sentinels,
const char* enable_features_flag_name,
const char* disable_features_flag_name) {
std::set<std::string> enabled_entries;
std::map<std::string, SwitchEntry> name_to_switch_map;
GenerateFlagsToSwitchesMapping(flags_storage, &enabled_entries,
&name_to_switch_map);
AddSwitchesToCommandLine(enabled_entries, name_to_switch_map, sentinels,
command_line, enable_features_flag_name,
disable_features_flag_name);
}
std::set<std::string> FlagsState::GetSwitchesFromFlags(
FlagsStorage* flags_storage) {
std::set<std::string> enabled_entries;
std::map<std::string, SwitchEntry> name_to_switch_map;
GenerateFlagsToSwitchesMapping(flags_storage, &enabled_entries,
&name_to_switch_map);
std::set<std::string> switches;
for (const std::string& entry_name : enabled_entries) {
const auto& entry_it = name_to_switch_map.find(entry_name);
DCHECK(entry_it != name_to_switch_map.end());
const SwitchEntry& entry = entry_it->second;
if (!entry.switch_name.empty())
switches.insert("--" + entry.switch_name);
}
return switches;
}
std::set<std::string> FlagsState::GetFeaturesFromFlags(
FlagsStorage* flags_storage) {
std::set<std::string> enabled_entries;
std::map<std::string, SwitchEntry> name_to_switch_map;
GenerateFlagsToSwitchesMapping(flags_storage, &enabled_entries,
&name_to_switch_map);
std::set<std::string> features;
for (const std::string& entry_name : enabled_entries) {
const auto& entry_it = name_to_switch_map.find(entry_name);
DCHECK(entry_it != name_to_switch_map.end());
const SwitchEntry& entry = entry_it->second;
if (!entry.feature_name.empty()) {
if (entry.feature_state)
features.insert(entry.feature_name + ":enabled");
else
features.insert(entry.feature_name + ":disabled");
}
}
return features;
}
bool FlagsState::IsRestartNeededToCommitChanges() {
return needs_restart_;
}
void FlagsState::SetFeatureEntryEnabled(FlagsStorage* flags_storage,
const std::string& internal_name,
bool enable) {
size_t at_index = internal_name.find(testing::kMultiSeparator);
if (at_index != std::string::npos) {
DCHECK(enable);
// We're being asked to enable a multi-choice entry. Disable the
// currently selected choice.
DCHECK_NE(at_index, 0u);
const std::string entry_name = internal_name.substr(0, at_index);
SetFeatureEntryEnabled(flags_storage, entry_name, false);
// And enable the new choice, if it is not the default first choice.
if (internal_name != entry_name + "@0") {
std::set<std::string> enabled_entries;
GetSanitizedEnabledFlags(flags_storage, &enabled_entries);
needs_restart_ |= enabled_entries.insert(internal_name).second;
flags_storage->SetFlags(enabled_entries);
}
return;
}
std::set<std::string> enabled_entries;
GetSanitizedEnabledFlags(flags_storage, &enabled_entries);
const FeatureEntry* e = nullptr;
for (size_t i = 0; i < num_feature_entries_; ++i) {
if (feature_entries_[i].internal_name == internal_name) {
e = feature_entries_ + i;
break;
}
}
DCHECK(e);
if (e->type == FeatureEntry::SINGLE_VALUE) {
if (enable)
needs_restart_ |= enabled_entries.insert(internal_name).second;
else
needs_restart_ |= (enabled_entries.erase(internal_name) > 0);
} else if (e->type == FeatureEntry::SINGLE_DISABLE_VALUE) {
if (!enable)
needs_restart_ |= enabled_entries.insert(internal_name).second;
else
needs_restart_ |= (enabled_entries.erase(internal_name) > 0);
} else {
if (enable) {
// Enable the first choice.
needs_restart_ |= enabled_entries.insert(e->NameForOption(0)).second;
} else {
// Find the currently enabled choice and disable it.
for (int i = 0; i < e->num_options; ++i) {
std::string choice_name = e->NameForOption(i);
if (enabled_entries.find(choice_name) != enabled_entries.end()) {
needs_restart_ = true;
enabled_entries.erase(choice_name);
// Continue on just in case there's a bug and more than one
// entry for this choice was enabled.
}
}
}
}
flags_storage->SetFlags(enabled_entries);
}
void FlagsState::RemoveFlagsSwitches(
std::map<std::string, base::CommandLine::StringType>* switch_list) {
for (const auto& entry : flags_switches_)
switch_list->erase(entry.first);
// If feature entries were added to --enable-features= or --disable-features=
// lists, remove them here while preserving existing values.
for (const auto& entry : appended_switches_) {
const auto& switch_name = entry.first;
const auto& switch_added_values = entry.second;
// The below is either a std::string or a base::string16 based on platform.
const auto& existing_value = (*switch_list)[switch_name];
#if defined(OS_WIN)
const std::string existing_value_utf8 = base::UTF16ToUTF8(existing_value);
#else
const std::string& existing_value_utf8 = existing_value;
#endif
std::vector<base::StringPiece> features =
base::FeatureList::SplitFeatureListString(existing_value_utf8);
std::vector<base::StringPiece> remaining_features;
// For any featrue name in |features| that is not in |switch_added_values| -
// i.e. it wasn't added by about_flags code, add it to |remaining_features|.
for (const auto& feature : features) {
if (!base::ContainsKey(switch_added_values, feature.as_string()))
remaining_features.push_back(feature);
}
// Either remove the flag entirely if |remaining_features| is empty, or set
// the new list.
if (remaining_features.empty()) {
switch_list->erase(switch_name);
} else {
std::string switch_value = base::JoinString(remaining_features, ",");
#if defined(OS_WIN)
(*switch_list)[switch_name] = base::UTF8ToUTF16(switch_value);
#else
(*switch_list)[switch_name] = switch_value;
#endif
}
}
}
void FlagsState::ResetAllFlags(FlagsStorage* flags_storage) {
needs_restart_ = true;
std::set<std::string> no_entries;
flags_storage->SetFlags(no_entries);
}
void FlagsState::Reset() {
needs_restart_ = false;
flags_switches_.clear();
appended_switches_.clear();
}
std::vector<std::string> FlagsState::RegisterAllFeatureVariationParameters(
FlagsStorage* flags_storage,
base::FeatureList* feature_list) {
std::set<std::string> enabled_entries;
GetSanitizedEnabledFlagsForCurrentPlatform(flags_storage, &enabled_entries);
std::vector<std::string> variation_ids;
std::map<std::string, std::set<std::string>> enabled_features_by_trial_name;
std::map<std::string, std::map<std::string, std::string>>
params_by_trial_name;
// First collect all the data for each trial.
for (size_t i = 0; i < num_feature_entries_; ++i) {
const FeatureEntry& e = feature_entries_[i];
if (e.type == FeatureEntry::FEATURE_WITH_PARAMS_VALUE) {
for (int j = 0; j < e.num_options; ++j) {
if (e.StateForOption(j) == FeatureEntry::FeatureState::ENABLED &&
enabled_entries.count(e.NameForOption(j))) {
std::string trial_name = e.feature_trial_name;
// The user has chosen to enable the feature by this option.
enabled_features_by_trial_name[trial_name].insert(e.feature->name);
const FeatureEntry::FeatureVariation* variation =
e.VariationForOption(j);
if (!variation)
continue;
// The selected variation is non-default, collect its params & id.
for (int i = 0; i < variation->num_params; ++i) {
auto insert_result = params_by_trial_name[trial_name].insert(
std::make_pair(variation->params[i].param_name,
variation->params[i].param_value));
DCHECK(insert_result.second)
<< "Multiple values for the same parameter '"
<< variation->params[i].param_name
<< "' are specified in chrome://flags!";
}
if (variation->variation_id)
variation_ids.push_back(variation->variation_id);
}
}
}
}
// Now create the trials and associate the features to them.
for (const auto& kv : enabled_features_by_trial_name) {
const std::string& trial_name = kv.first;
const std::set<std::string>& trial_features = kv.second;
base::FieldTrial* field_trial = RegisterFeatureVariationParameters(
trial_name, params_by_trial_name[trial_name]);
if (!field_trial)
continue;
for (const std::string& feature_name : trial_features) {
feature_list->RegisterFieldTrialOverride(
feature_name,
base::FeatureList::OverrideState::OVERRIDE_ENABLE_FEATURE,
field_trial);
}
}
return variation_ids;
}
void FlagsState::GetFlagFeatureEntries(
FlagsStorage* flags_storage,
FlagAccess access,
base::ListValue* supported_entries,
base::ListValue* unsupported_entries,
base::Callback<bool(const FeatureEntry&)> skip_feature_entry) {
std::set<std::string> enabled_entries;
GetSanitizedEnabledFlags(flags_storage, &enabled_entries);
int current_platform = GetCurrentPlatform();
for (size_t i = 0; i < num_feature_entries_; ++i) {
const FeatureEntry& entry = feature_entries_[i];
if (skip_feature_entry.Run(entry))
continue;
std::unique_ptr<base::DictionaryValue> data(new base::DictionaryValue());
data->SetString("internal_name", entry.internal_name);
data->SetString("name", base::StringPiece(entry.visible_name));
data->SetString("description",
base::StringPiece(entry.visible_description));
auto supported_platforms = base::MakeUnique<base::ListValue>();
AddOsStrings(entry.supported_platforms, supported_platforms.get());
data->Set("supported_platforms", std::move(supported_platforms));
// True if the switch is not currently passed.
bool is_default_value = IsDefaultValue(entry, enabled_entries);
data->SetBoolean("is_default", is_default_value);
switch (entry.type) {
case FeatureEntry::SINGLE_VALUE:
case FeatureEntry::SINGLE_DISABLE_VALUE:
data->SetBoolean(
"enabled",
(!is_default_value && entry.type == FeatureEntry::SINGLE_VALUE) ||
(is_default_value &&
entry.type == FeatureEntry::SINGLE_DISABLE_VALUE));
break;
case FeatureEntry::MULTI_VALUE:
case FeatureEntry::ENABLE_DISABLE_VALUE:
case FeatureEntry::FEATURE_VALUE:
case FeatureEntry::FEATURE_WITH_PARAMS_VALUE:
data->Set("options", CreateOptionsData(entry, enabled_entries));
break;
}
bool supported = (entry.supported_platforms & current_platform) != 0;
#if defined(OS_CHROMEOS)
if (access == kOwnerAccessToFlags &&
(entry.supported_platforms & kOsCrOSOwnerOnly) != 0) {
supported = true;
}
#endif
#if defined(OS_IOS)
if (access == kAppleReviewAccessToFlags)
supported = ((entry.supported_platforms & kOsIosAppleReview) != 0);
#endif
if (supported)
supported_entries->Append(std::move(data));
else
unsupported_entries->Append(std::move(data));
}
}
// static
int FlagsState::GetCurrentPlatform() {
#if defined(OS_IOS) // Needs to be before the OS_MACOSX check.
return kOsIos;
#elif defined(OS_MACOSX)
return kOsMac;
#elif defined(OS_WIN)
return kOsWin;
#elif defined(OS_CHROMEOS) // Needs to be before the OS_LINUX check.
return kOsCrOS;
#elif defined(OS_LINUX) || defined(OS_OPENBSD)
return kOsLinux;
#elif defined(OS_ANDROID)
return kOsAndroid;
#else
#error Unknown platform
#endif
}
// static
bool FlagsState::AreSwitchesIdenticalToCurrentCommandLine(
const base::CommandLine& new_cmdline,
const base::CommandLine& active_cmdline,
std::set<base::CommandLine::StringType>* out_difference,
const char* extra_flag_sentinel_begin_flag_name,
const char* extra_flag_sentinel_end_flag_name) {
std::set<base::CommandLine::StringType> new_flags =
ExtractFlagsFromCommandLine(new_cmdline,
extra_flag_sentinel_begin_flag_name,
extra_flag_sentinel_end_flag_name);
std::set<base::CommandLine::StringType> active_flags =
ExtractFlagsFromCommandLine(active_cmdline,
extra_flag_sentinel_begin_flag_name,
extra_flag_sentinel_end_flag_name);
bool result = false;
// Needed because std::equal doesn't check if the 2nd set is empty.
if (new_flags.size() == active_flags.size()) {
result =
std::equal(new_flags.begin(), new_flags.end(), active_flags.begin());
}
if (out_difference && !result) {
std::set_symmetric_difference(
new_flags.begin(), new_flags.end(), active_flags.begin(),
active_flags.end(),
std::inserter(*out_difference, out_difference->begin()));
}
return result;
}
void FlagsState::AddSwitchMapping(
const std::string& key,
const std::string& switch_name,
const std::string& switch_value,
std::map<std::string, SwitchEntry>* name_to_switch_map) {
DCHECK(!base::ContainsKey(*name_to_switch_map, key));
SwitchEntry* entry = &(*name_to_switch_map)[key];
entry->switch_name = switch_name;
entry->switch_value = switch_value;
}
void FlagsState::AddFeatureMapping(
const std::string& key,
const std::string& feature_name,
bool feature_state,
std::map<std::string, SwitchEntry>* name_to_switch_map) {
DCHECK(!base::ContainsKey(*name_to_switch_map, key));
SwitchEntry* entry = &(*name_to_switch_map)[key];
entry->feature_name = feature_name;
entry->feature_state = feature_state;
}
void FlagsState::AddSwitchesToCommandLine(
const std::set<std::string>& enabled_entries,
const std::map<std::string, SwitchEntry>& name_to_switch_map,
SentinelsMode sentinels,
base::CommandLine* command_line,
const char* enable_features_flag_name,
const char* disable_features_flag_name) {
std::map<std::string, bool> feature_switches;
if (sentinels == kAddSentinels) {
command_line->AppendSwitch(switches::kFlagSwitchesBegin);
flags_switches_[switches::kFlagSwitchesBegin] = std::string();
}
for (const std::string& entry_name : enabled_entries) {
const auto& entry_it = name_to_switch_map.find(entry_name);
if (entry_it == name_to_switch_map.end()) {
NOTREACHED();
continue;
}
const SwitchEntry& entry = entry_it->second;
if (!entry.feature_name.empty()) {
feature_switches[entry.feature_name] = entry.feature_state;
} else if (!entry.switch_name.empty()) {
command_line->AppendSwitchASCII(entry.switch_name, entry.switch_value);
flags_switches_[entry.switch_name] = entry.switch_value;
}
// If an entry doesn't match either of the above, then it is likely the
// default entry for a FEATURE_VALUE entry. Safe to ignore.
}
if (!feature_switches.empty()) {
MergeFeatureCommandLineSwitch(feature_switches, enable_features_flag_name,
true, command_line);
MergeFeatureCommandLineSwitch(feature_switches, disable_features_flag_name,
false, command_line);
}
if (sentinels == kAddSentinels) {
command_line->AppendSwitch(switches::kFlagSwitchesEnd);
flags_switches_[switches::kFlagSwitchesEnd] = std::string();
}
}
void FlagsState::MergeFeatureCommandLineSwitch(
const std::map<std::string, bool>& feature_switches,
const char* switch_name,
bool feature_state,
base::CommandLine* command_line) {
std::string original_switch_value =
command_line->GetSwitchValueASCII(switch_name);
std::vector<base::StringPiece> features =
base::FeatureList::SplitFeatureListString(original_switch_value);
// Only add features that don't already exist in the lists.
// Note: The base::ContainsValue() call results in O(n^2) performance, but in
// practice n should be very small.
for (const auto& entry : feature_switches) {
if (entry.second == feature_state &&
!base::ContainsValue(features, entry.first)) {
features.push_back(entry.first);
appended_switches_[switch_name].insert(entry.first);
}
}
// Update the switch value only if it didn't change. This avoids setting an
// empty list or duplicating the same list (since AppendSwitch() adds the
// switch to the end but doesn't remove previous ones).
std::string switch_value = base::JoinString(features, ",");
if (switch_value != original_switch_value)
command_line->AppendSwitchASCII(switch_name, switch_value);
}
void FlagsState::SanitizeList(FlagsStorage* flags_storage) {
std::set<std::string> known_entries;
for (size_t i = 0; i < num_feature_entries_; ++i) {
DCHECK(ValidateFeatureEntry(feature_entries_[i]));
AddInternalName(feature_entries_[i], &known_entries);
}
std::set<std::string> enabled_entries = flags_storage->GetFlags();
std::set<std::string> new_enabled_entries =
base::STLSetIntersection<std::set<std::string>>(known_entries,
enabled_entries);
if (new_enabled_entries != enabled_entries)
flags_storage->SetFlags(new_enabled_entries);
}
void FlagsState::GetSanitizedEnabledFlags(FlagsStorage* flags_storage,
std::set<std::string>* result) {
SanitizeList(flags_storage);
*result = flags_storage->GetFlags();
}
void FlagsState::GetSanitizedEnabledFlagsForCurrentPlatform(
FlagsStorage* flags_storage,
std::set<std::string>* result) {
GetSanitizedEnabledFlags(flags_storage, result);
// Filter out any entries that aren't enabled on the current platform. We
// don't remove these from prefs else syncing to a platform with a different
// set of entries would be lossy.
std::set<std::string> platform_entries;
int current_platform = GetCurrentPlatform();
for (size_t i = 0; i < num_feature_entries_; ++i) {
const FeatureEntry& entry = feature_entries_[i];
if (entry.supported_platforms & current_platform)
AddInternalName(entry, &platform_entries);
#if defined(OS_CHROMEOS)
if (feature_entries_[i].supported_platforms & kOsCrOSOwnerOnly)
AddInternalName(entry, &platform_entries);
#endif
}
std::set<std::string> new_enabled_entries =
base::STLSetIntersection<std::set<std::string>>(platform_entries,
*result);
result->swap(new_enabled_entries);
}
void FlagsState::GenerateFlagsToSwitchesMapping(
FlagsStorage* flags_storage,
std::set<std::string>* enabled_entries,
std::map<std::string, SwitchEntry>* name_to_switch_map) {
GetSanitizedEnabledFlagsForCurrentPlatform(flags_storage, enabled_entries);
for (size_t i = 0; i < num_feature_entries_; ++i) {
const FeatureEntry& e = feature_entries_[i];
switch (e.type) {
case FeatureEntry::SINGLE_VALUE:
case FeatureEntry::SINGLE_DISABLE_VALUE:
AddSwitchMapping(e.internal_name, e.command_line_switch,
e.command_line_value, name_to_switch_map);
break;
case FeatureEntry::MULTI_VALUE:
for (int j = 0; j < e.num_options; ++j) {
AddSwitchMapping(
e.NameForOption(j), e.ChoiceForOption(j).command_line_switch,
e.ChoiceForOption(j).command_line_value, name_to_switch_map);
}
break;
case FeatureEntry::ENABLE_DISABLE_VALUE:
AddSwitchMapping(e.NameForOption(0), std::string(), std::string(),
name_to_switch_map);
AddSwitchMapping(e.NameForOption(1), e.command_line_switch,
e.command_line_value, name_to_switch_map);
AddSwitchMapping(e.NameForOption(2), e.disable_command_line_switch,
e.disable_command_line_value, name_to_switch_map);
break;
case FeatureEntry::FEATURE_VALUE:
case FeatureEntry::FEATURE_WITH_PARAMS_VALUE:
for (int j = 0; j < e.num_options; ++j) {
FeatureEntry::FeatureState state = e.StateForOption(j);
if (state == FeatureEntry::FeatureState::DEFAULT) {
AddFeatureMapping(e.NameForOption(j), std::string(), false,
name_to_switch_map);
} else {
AddFeatureMapping(e.NameForOption(j), e.feature->name,
state == FeatureEntry::FeatureState::ENABLED,
name_to_switch_map);
}
}
break;
}
}
}
} // namespace flags_ui