Index: chrome/browser/about_flags.cc |
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc |
index f717a87017b03f90e8a7d018cb8681df4983a5dc..0dfccb64cb9f000ff3088be1c0831995ed1fa810 100644 |
--- a/chrome/browser/about_flags.cc |
+++ b/chrome/browser/about_flags.cc |
@@ -10,6 +10,7 @@ |
#include <utility> |
#include "base/command_line.h" |
+#include "base/feature_list.h" |
#include "base/memory/singleton.h" |
#include "base/metrics/sparse_histogram.h" |
#include "base/stl_util.h" |
@@ -83,26 +84,31 @@ |
namespace about_flags { |
-// Macros to simplify specifying the type. |
-#define SINGLE_VALUE_TYPE_AND_VALUE(command_line_switch, switch_value) \ |
- FeatureEntry::SINGLE_VALUE, \ |
- command_line_switch, switch_value, NULL, NULL, NULL, 0 |
+// Macros to simplify specifying the type. Please refer to the comments on |
+// FeatureEntry::Type in the header file, which explain the different entry |
+// types and when they should be used. |
+#define SINGLE_VALUE_TYPE_AND_VALUE(command_line_switch, switch_value) \ |
+ FeatureEntry::SINGLE_VALUE, command_line_switch, switch_value, nullptr, \ |
+ nullptr, nullptr, nullptr, 0 |
#define SINGLE_VALUE_TYPE(command_line_switch) \ |
SINGLE_VALUE_TYPE_AND_VALUE(command_line_switch, "") |
#define SINGLE_DISABLE_VALUE_TYPE_AND_VALUE(command_line_switch, switch_value) \ |
- FeatureEntry::SINGLE_DISABLE_VALUE, \ |
- command_line_switch, switch_value, NULL, NULL, NULL, 0 |
+ FeatureEntry::SINGLE_DISABLE_VALUE, command_line_switch, switch_value, \ |
+ nullptr, nullptr, nullptr, nullptr, 0 |
#define SINGLE_DISABLE_VALUE_TYPE(command_line_switch) \ |
SINGLE_DISABLE_VALUE_TYPE_AND_VALUE(command_line_switch, "") |
-#define ENABLE_DISABLE_VALUE_TYPE_AND_VALUE(enable_switch, enable_value, \ |
+#define ENABLE_DISABLE_VALUE_TYPE_AND_VALUE(enable_switch, enable_value, \ |
disable_switch, disable_value) \ |
- FeatureEntry::ENABLE_DISABLE_VALUE, enable_switch, enable_value, \ |
- disable_switch, disable_value, NULL, 3 |
+ FeatureEntry::ENABLE_DISABLE_VALUE, enable_switch, enable_value, \ |
+ disable_switch, disable_value, nullptr, nullptr, 3 |
#define ENABLE_DISABLE_VALUE_TYPE(enable_switch, disable_switch) \ |
ENABLE_DISABLE_VALUE_TYPE_AND_VALUE(enable_switch, "", disable_switch, "") |
-#define MULTI_VALUE_TYPE(choices) \ |
- FeatureEntry::MULTI_VALUE, NULL, NULL, NULL, NULL, choices, \ |
- arraysize(choices) |
+#define MULTI_VALUE_TYPE(choices) \ |
+ FeatureEntry::MULTI_VALUE, nullptr, nullptr, nullptr, nullptr, nullptr, \ |
+ choices, arraysize(choices) |
+#define FEATURE_VALUE_TYPE(feature) \ |
+ FeatureEntry::FEATURE_VALUE, nullptr, nullptr, nullptr, nullptr, \ |
+ feature.name, nullptr, 3 |
namespace { |
@@ -2160,6 +2166,56 @@ class FlagsState { |
} |
private: |
+ // Keeps track of affected switches for each FeatureEntry, based on which |
+ // choice is selected for it. |
+ struct 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) {} |
+ }; |
+ |
+ // Adds mapping to |name_to_switch_map| to set the given switch name/value. |
+ void AddSwitchMapping(const std::string& key, |
+ const std::string& switch_name, |
+ const std::string& switch_value, |
+ std::map<std::string, SwitchEntry>* name_to_switch_map); |
+ |
+ // Adds mapping to |name_to_switch_map| to toggle base::Feature |feature_name| |
+ // to state |feature_state|. |
+ void AddFeatureMapping( |
+ const std::string& key, |
+ const std::string& feature_name, |
+ bool feature_state, |
+ std::map<std::string, SwitchEntry>* name_to_switch_map); |
+ |
+ // Updates the switches in |command_line| by applying the modifications |
+ // specified in |name_to_switch_map| for each entry in |enabled_entries|. |
+ void AddSwitchesToCommandLine( |
+ const std::set<std::string>& enabled_entries, |
+ const std::map<std::string, SwitchEntry>& name_to_switch_map, |
+ SentinelsMode sentinels, |
+ base::CommandLine* command_line); |
+ |
+ // Updates |command_line| by merging the value of the --enable-features= or |
+ // --disable-features= list (per the |switch_name| param) with corresponding |
+ // entries in |feature_switches| that have value |feature_state|. Keeps track |
+ // of the changes by updating |appended_switches|. |
+ void MergeFeatureCommandLineSwitch( |
+ const std::map<std::string, bool>& feature_switches, |
+ const char* switch_name, |
+ bool feature_state, |
+ base::CommandLine* command_line); |
+ |
// Removes all entries from prefs::kEnabledLabsExperiments that are unknown, |
// to prevent this list to become very long as entries are added and removed. |
void SanitizeList(flags_ui::FlagsStorage* flags_storage); |
@@ -2179,19 +2235,26 @@ class FlagsState { |
bool needs_restart_; |
std::map<std::string, std::string> flags_switches_; |
+ // Map from switch name to a set of string, that keeps track which strings |
+ // were appended to existing (list value) switches. |
+ std::map<std::string, std::set<std::string>> appended_switches_; |
+ |
DISALLOW_COPY_AND_ASSIGN(FlagsState); |
}; |
// Adds the internal names for the specified entry to |names|. |
void AddInternalName(const FeatureEntry& e, std::set<std::string>* names) { |
- if (e.type == FeatureEntry::SINGLE_VALUE || |
- e.type == FeatureEntry::SINGLE_DISABLE_VALUE) { |
- names->insert(e.internal_name); |
- } else { |
- DCHECK(e.type == FeatureEntry::MULTI_VALUE || |
- e.type == FeatureEntry::ENABLE_DISABLE_VALUE); |
- for (int i = 0; i < e.num_choices; ++i) |
- names->insert(e.NameForChoice(i)); |
+ 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: |
+ for (int i = 0; i < e.num_choices; ++i) |
+ names->insert(e.NameForChoice(i)); |
+ break; |
} |
} |
@@ -2203,13 +2266,13 @@ bool ValidateFeatureEntry(const FeatureEntry& e) { |
case FeatureEntry::SINGLE_DISABLE_VALUE: |
DCHECK_EQ(0, e.num_choices); |
DCHECK(!e.choices); |
- break; |
+ return true; |
case FeatureEntry::MULTI_VALUE: |
DCHECK_GT(e.num_choices, 0); |
DCHECK(e.choices); |
DCHECK(e.choices[0].command_line_switch); |
DCHECK_EQ('\0', e.choices[0].command_line_switch[0]); |
- break; |
+ return true; |
case FeatureEntry::ENABLE_DISABLE_VALUE: |
DCHECK_EQ(3, e.num_choices); |
DCHECK(!e.choices); |
@@ -2217,11 +2280,15 @@ bool ValidateFeatureEntry(const FeatureEntry& e) { |
DCHECK(e.command_line_value); |
DCHECK(e.disable_command_line_switch); |
DCHECK(e.disable_command_line_value); |
- break; |
- default: |
- NOTREACHED(); |
+ return true; |
+ case FeatureEntry::FEATURE_VALUE: |
+ DCHECK_EQ(3, e.num_choices); |
+ DCHECK(!e.choices); |
+ DCHECK(e.feature_name); |
+ return true; |
} |
- return true; |
+ NOTREACHED(); |
+ return false; |
} |
void FlagsState::SanitizeList(flags_ui::FlagsStorage* flags_storage) { |
@@ -2345,14 +2412,14 @@ bool IsDefaultValue( |
return enabled_entries.count(entry.internal_name) == 0; |
case FeatureEntry::MULTI_VALUE: |
case FeatureEntry::ENABLE_DISABLE_VALUE: |
+ case FeatureEntry::FEATURE_VALUE: |
for (int i = 0; i < entry.num_choices; ++i) { |
if (enabled_entries.count(entry.NameForChoice(i)) > 0) |
return false; |
} |
- break; |
- default: |
- NOTREACHED(); |
+ return true; |
} |
+ NOTREACHED(); |
return true; |
} |
@@ -2361,7 +2428,8 @@ base::Value* CreateChoiceData( |
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::ENABLE_DISABLE_VALUE || |
+ entry.type == FeatureEntry::FEATURE_VALUE); |
base::ListValue* result = new base::ListValue; |
for (int i = 0; i < entry.num_choices; ++i) { |
base::DictionaryValue* value = new base::DictionaryValue; |
@@ -2378,7 +2446,8 @@ base::Value* CreateChoiceData( |
std::string FeatureEntry::NameForChoice(int index) const { |
DCHECK(type == FeatureEntry::MULTI_VALUE || |
- type == FeatureEntry::ENABLE_DISABLE_VALUE); |
+ type == FeatureEntry::ENABLE_DISABLE_VALUE || |
+ type == FeatureEntry::FEATURE_VALUE); |
DCHECK_LT(index, num_choices); |
return std::string(internal_name) + testing::kMultiSeparator + |
base::IntToString(index); |
@@ -2386,10 +2455,12 @@ std::string FeatureEntry::NameForChoice(int index) const { |
base::string16 FeatureEntry::DescriptionForChoice(int index) const { |
DCHECK(type == FeatureEntry::MULTI_VALUE || |
- type == FeatureEntry::ENABLE_DISABLE_VALUE); |
+ type == FeatureEntry::ENABLE_DISABLE_VALUE || |
+ type == FeatureEntry::FEATURE_VALUE); |
DCHECK_LT(index, num_choices); |
int description_id; |
- if (type == FeatureEntry::ENABLE_DISABLE_VALUE) { |
+ if (type == FeatureEntry::ENABLE_DISABLE_VALUE || |
+ type == FeatureEntry::FEATURE_VALUE) { |
const int kEnableDisableDescriptionIds[] = { |
IDS_GENERIC_EXPERIMENT_CHOICE_DEFAULT, |
IDS_GENERIC_EXPERIMENT_CHOICE_ENABLED, |
@@ -2513,8 +2584,8 @@ bool IsRestartNeededToCommitChanges() { |
} |
void SetFeatureEntryEnabled(flags_ui::FlagsStorage* flags_storage, |
- const std::string& internal_name, |
- bool enable) { |
+ const std::string& internal_name, |
+ bool enable) { |
FlagsState::GetInstance()->SetFeatureEntryEnabled(flags_storage, |
internal_name, enable); |
} |
@@ -2595,17 +2666,6 @@ void ReportCustomFlags(const std::string& uma_histogram_hame, |
namespace { |
-typedef std::map<std::string, std::pair<std::string, std::string> > |
- NameToSwitchAndValueMap; |
- |
-void SetFlagToSwitchMapping(const std::string& key, |
- const std::string& switch_name, |
- const std::string& switch_value, |
- NameToSwitchAndValueMap* name_to_switch_map) { |
- DCHECK(name_to_switch_map->end() == name_to_switch_map->find(key)); |
- (*name_to_switch_map)[key] = std::make_pair(switch_name, switch_value); |
-} |
- |
void FlagsState::ConvertFlagsToSwitches(flags_ui::FlagsStorage* flags_storage, |
base::CommandLine* command_line, |
SentinelsMode sentinels) { |
@@ -2614,62 +2674,45 @@ void FlagsState::ConvertFlagsToSwitches(flags_ui::FlagsStorage* flags_storage, |
std::set<std::string> enabled_entries; |
- GetSanitizedEnabledFlagsForCurrentPlatform(flags_storage, |
- &enabled_entries); |
+ GetSanitizedEnabledFlagsForCurrentPlatform(flags_storage, &enabled_entries); |
- NameToSwitchAndValueMap name_to_switch_map; |
+ std::map<std::string, SwitchEntry> name_to_switch_map; |
for (size_t i = 0; i < num_feature_entries; ++i) { |
const FeatureEntry& e = feature_entries[i]; |
- if (e.type == FeatureEntry::SINGLE_VALUE || |
- e.type == FeatureEntry::SINGLE_DISABLE_VALUE) { |
- SetFlagToSwitchMapping(e.internal_name, e.command_line_switch, |
- e.command_line_value, &name_to_switch_map); |
- } else if (e.type == FeatureEntry::MULTI_VALUE) { |
- for (int j = 0; j < e.num_choices; ++j) { |
- SetFlagToSwitchMapping(e.NameForChoice(j), |
- e.choices[j].command_line_switch, |
- e.choices[j].command_line_value, |
- &name_to_switch_map); |
- } |
- } else { |
- DCHECK_EQ(e.type, FeatureEntry::ENABLE_DISABLE_VALUE); |
- SetFlagToSwitchMapping(e.NameForChoice(0), std::string(), std::string(), |
- &name_to_switch_map); |
- SetFlagToSwitchMapping(e.NameForChoice(1), e.command_line_switch, |
- e.command_line_value, &name_to_switch_map); |
- SetFlagToSwitchMapping(e.NameForChoice(2), e.disable_command_line_switch, |
- e.disable_command_line_value, &name_to_switch_map); |
+ 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_choices; ++j) { |
+ AddSwitchMapping(e.NameForChoice(j), e.choices[j].command_line_switch, |
+ e.choices[j].command_line_value, |
+ &name_to_switch_map); |
+ } |
+ break; |
+ case FeatureEntry::ENABLE_DISABLE_VALUE: |
+ AddSwitchMapping(e.NameForChoice(0), std::string(), std::string(), |
+ &name_to_switch_map); |
+ AddSwitchMapping(e.NameForChoice(1), e.command_line_switch, |
+ e.command_line_value, &name_to_switch_map); |
+ AddSwitchMapping(e.NameForChoice(2), e.disable_command_line_switch, |
+ e.disable_command_line_value, &name_to_switch_map); |
+ break; |
+ case FeatureEntry::FEATURE_VALUE: |
+ AddFeatureMapping(e.NameForChoice(0), std::string(), false, |
+ &name_to_switch_map); |
+ AddFeatureMapping(e.NameForChoice(1), e.feature_name, true, |
+ &name_to_switch_map); |
+ AddFeatureMapping(e.NameForChoice(2), e.feature_name, false, |
+ &name_to_switch_map); |
+ break; |
} |
} |
- if (sentinels == kAddSentinels) { |
- command_line->AppendSwitch(switches::kFlagSwitchesBegin); |
- flags_switches_.insert( |
- std::pair<std::string, std::string>(switches::kFlagSwitchesBegin, |
- std::string())); |
- } |
- for (const std::string& entry_name : enabled_entries) { |
- NameToSwitchAndValueMap::const_iterator name_to_switch_it = |
- name_to_switch_map.find(entry_name); |
- if (name_to_switch_it == name_to_switch_map.end()) { |
- NOTREACHED(); |
- continue; |
- } |
- |
- const std::pair<std::string, std::string>& |
- switch_and_value_pair = name_to_switch_it->second; |
- |
- CHECK(!switch_and_value_pair.first.empty()); |
- command_line->AppendSwitchASCII(switch_and_value_pair.first, |
- switch_and_value_pair.second); |
- flags_switches_[switch_and_value_pair.first] = switch_and_value_pair.second; |
- } |
- if (sentinels == kAddSentinels) { |
- command_line->AppendSwitch(switches::kFlagSwitchesEnd); |
- flags_switches_.insert( |
- std::pair<std::string, std::string>(switches::kFlagSwitchesEnd, |
- std::string())); |
- } |
+ AddSwitchesToCommandLine(enabled_entries, name_to_switch_map, sentinels, |
+ command_line); |
} |
bool FlagsState::IsRestartNeededToCommitChanges() { |
@@ -2701,7 +2744,7 @@ void FlagsState::SetFeatureEntryEnabled(flags_ui::FlagsStorage* flags_storage, |
std::set<std::string> enabled_entries; |
GetSanitizedEnabledFlags(flags_storage, &enabled_entries); |
- const FeatureEntry* e = NULL; |
+ 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; |
@@ -2746,6 +2789,44 @@ 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<std::string> features = |
+ base::FeatureList::SplitFeatureListString(existing_value_utf8); |
+ std::vector<std::string> 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 std::string& feature : features) { |
+ if (!ContainsKey(switch_added_values, feature)) |
+ 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(flags_ui::FlagsStorage* flags_storage) { |
@@ -2758,6 +2839,7 @@ void FlagsState::ResetAllFlags(flags_ui::FlagsStorage* flags_storage) { |
void FlagsState::Reset() { |
needs_restart_ = false; |
flags_switches_.clear(); |
+ appended_switches_.clear(); |
} |
void FlagsState::SetFeatureEntries(const FeatureEntry* entries, size_t count) { |
@@ -2770,6 +2852,99 @@ const FeatureEntry* FlagsState::GetFeatureEntries(size_t* count) { |
return feature_entries; |
} |
+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(!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(!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) { |
+ 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, switches::kEnableFeatures, |
+ true, command_line); |
+ MergeFeatureCommandLineSwitch(feature_switches, switches::kDisableFeatures, |
+ 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<std::string> features = |
+ base::FeatureList::SplitFeatureListString(original_switch_value); |
+ // Only add features that don't already exist in the lists. |
+ // Note: The 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 && |
+ !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); |
+} |
+ |
} // namespace |
namespace testing { |