flags_state.cc 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986
  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. #include "components/flags_ui/flags_state.h"
  5. #include <algorithm>
  6. #include <memory>
  7. #include <utility>
  8. #include "base/callback.h"
  9. #include "base/containers/contains.h"
  10. #include "base/containers/span.h"
  11. #include "base/feature_list.h"
  12. #include "base/feature_list_buildflags.h"
  13. #include "base/logging.h"
  14. #include "base/metrics/field_trial.h"
  15. #include "base/stl_util.h"
  16. #include "base/strings/strcat.h"
  17. #include "base/strings/string_piece.h"
  18. #include "base/strings/string_tokenizer.h"
  19. #include "base/strings/string_util.h"
  20. #include "base/strings/utf_string_conversions.h"
  21. #include "base/values.h"
  22. #include "build/build_config.h"
  23. #include "components/flags_ui/feature_entry.h"
  24. #include "components/flags_ui/flags_storage.h"
  25. #include "components/flags_ui/flags_ui_switches.h"
  26. #include "components/variations/field_trial_config/field_trial_util.h"
  27. #include "components/variations/variations_associated_data.h"
  28. #include "components/variations/variations_switches.h"
  29. #include "ui/base/l10n/l10n_util.h"
  30. #include "url/gurl.h"
  31. #include "url/origin.h"
  32. namespace flags_ui {
  33. namespace internal {
  34. const char kTrialGroupAboutFlags[] = "AboutFlags";
  35. } // namespace internal
  36. namespace {
  37. // Separator used for origin list values. The list of origins provided from
  38. // the command line or from the text input in chrome://flags are concatenated
  39. // using this separator. The value is then appended as a command line switch
  40. // and saved in the dictionary pref (kAboutFlagsOriginLists).
  41. // E.g. --isolate_origins=http://example1.net,http://example2.net
  42. const char kOriginListValueSeparator[] = ",";
  43. const struct {
  44. unsigned bit;
  45. const char* const name;
  46. } kBitsToOs[] = {
  47. {kOsMac, "Mac"}, {kOsWin, "Windows"},
  48. {kOsLinux, "Linux"}, {kOsCrOS, "ChromeOS"},
  49. {kOsAndroid, "Android"}, {kOsCrOSOwnerOnly, "ChromeOS (owner only)"},
  50. {kOsIos, "iOS"}, {kOsFuchsia, "Fuchsia"},
  51. {kOsLacros, "Lacros"},
  52. };
  53. // Adds a |StringValue| to |list| for each platform where |bitmask| indicates
  54. // whether the entry is available on that platform.
  55. void AddOsStrings(unsigned bitmask, base::Value::List* list) {
  56. for (const auto& entry : kBitsToOs) {
  57. if (bitmask & entry.bit)
  58. list->Append(entry.name);
  59. }
  60. }
  61. // Returns true if none of this entry's options have been enabled.
  62. bool IsDefaultValue(const FeatureEntry& entry,
  63. const std::set<std::string>& enabled_entries) {
  64. switch (entry.type) {
  65. case FeatureEntry::SINGLE_VALUE:
  66. case FeatureEntry::SINGLE_DISABLE_VALUE:
  67. case FeatureEntry::ORIGIN_LIST_VALUE:
  68. return enabled_entries.count(entry.internal_name) == 0;
  69. case FeatureEntry::MULTI_VALUE:
  70. case FeatureEntry::ENABLE_DISABLE_VALUE:
  71. case FeatureEntry::FEATURE_VALUE:
  72. case FeatureEntry::FEATURE_WITH_PARAMS_VALUE:
  73. #if BUILDFLAG(IS_CHROMEOS_ASH)
  74. case FeatureEntry::PLATFORM_FEATURE_NAME_VALUE:
  75. case FeatureEntry::PLATFORM_FEATURE_NAME_WITH_PARAMS_VALUE:
  76. #endif // BUILDFLAG(IS_CHROMEOS_ASH)
  77. for (int i = 0; i < entry.NumOptions(); ++i) {
  78. if (enabled_entries.count(entry.NameForOption(i)) > 0)
  79. return false;
  80. }
  81. return true;
  82. }
  83. NOTREACHED();
  84. return true;
  85. }
  86. // Returns the Value::List representing the choice data in the specified entry.
  87. base::Value::List CreateOptionsData(
  88. const FeatureEntry& entry,
  89. const std::set<std::string>& enabled_entries) {
  90. DCHECK(entry.type == FeatureEntry::MULTI_VALUE ||
  91. entry.type == FeatureEntry::ENABLE_DISABLE_VALUE ||
  92. entry.type == FeatureEntry::FEATURE_VALUE ||
  93. entry.type == FeatureEntry::FEATURE_WITH_PARAMS_VALUE
  94. #if BUILDFLAG(IS_CHROMEOS_ASH)
  95. || entry.type == FeatureEntry::PLATFORM_FEATURE_NAME_VALUE ||
  96. entry.type == FeatureEntry::PLATFORM_FEATURE_NAME_WITH_PARAMS_VALUE
  97. #endif // BUILDFLAG(IS_CHROMEOS_ASH)
  98. );
  99. base::Value::List result;
  100. for (int i = 0; i < entry.NumOptions(); ++i) {
  101. base::Value::Dict dict;
  102. const std::string name = entry.NameForOption(i);
  103. dict.Set("internal_name", name);
  104. dict.Set("description", entry.DescriptionForOption(i));
  105. dict.Set("selected", enabled_entries.count(name) > 0);
  106. result.Append(std::move(dict));
  107. }
  108. return result;
  109. }
  110. // Registers variation parameters specified by |feature_variation_params| for
  111. // the field trial named |feature_trial_name|, unless a group for this trial has
  112. // already been created (e.g. via command-line switches that take precedence
  113. // over about:flags). In the trial, the function creates a new constant group
  114. // with the given |trail_group| name.
  115. base::FieldTrial* RegisterFeatureVariationParameters(
  116. const std::string& feature_trial_name,
  117. const std::map<std::string, std::string>& feature_variation_params,
  118. const std::string& trial_group) {
  119. bool success = variations::AssociateVariationParams(
  120. feature_trial_name, trial_group, feature_variation_params);
  121. if (!success)
  122. return nullptr;
  123. // Successful association also means that no group is created and selected
  124. // for the trial, yet. Thus, create the trial to select the group. This way,
  125. // the parameters cannot get overwritten in later phases (such as from the
  126. // server).
  127. base::FieldTrial* trial =
  128. base::FieldTrialList::CreateFieldTrial(feature_trial_name, trial_group);
  129. if (!trial) {
  130. DLOG(WARNING) << "Could not create the trial " << feature_trial_name
  131. << " with group " << trial_group;
  132. }
  133. return trial;
  134. }
  135. // Returns true if |value| is safe to include in a command line string in the
  136. // form of --flag=value.
  137. bool IsSafeValue(const std::string& value) {
  138. // Punctuation characters at the end ("-", ".", ":", "/") are allowed because
  139. // origins can contain those (e.g. http://example.test). Comma is allowed
  140. // because it's used as the separator character.
  141. static const char kAllowedChars[] =
  142. "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
  143. "abcdefghijklmnopqrstuvwxyz"
  144. "0123456789"
  145. "-.:/,";
  146. return value.find_first_not_of(kAllowedChars) == std::string::npos;
  147. }
  148. // Sanitizes |value| which contains a list of origins separated by whitespace
  149. // and/or comma. The sanitized vector of origins is intended to be added to the
  150. // command line, so this is a security critical operation: The sanitized value
  151. // must have no whitespaces, each individual origin must be separated by a
  152. // comma, and each origin must represent a url::Origin(). The list is not
  153. // reordered.
  154. std::vector<std::string> TokenizeOriginList(const std::string& value) {
  155. const std::string input = base::CollapseWhitespaceASCII(value, false);
  156. // Allow both space and comma as separators.
  157. const std::string delimiters = " ,";
  158. base::StringTokenizer tokenizer(input, delimiters);
  159. std::vector<std::string> origin_strings;
  160. while (tokenizer.GetNext()) {
  161. base::StringPiece token = tokenizer.token_piece();
  162. DCHECK(!token.empty());
  163. const GURL url(token);
  164. if (!url.is_valid() ||
  165. (!url.SchemeIsHTTPOrHTTPS() && !url.SchemeIsWSOrWSS())) {
  166. continue;
  167. }
  168. const std::string origin = url::Origin::Create(url).Serialize();
  169. if (!IsSafeValue(origin)) {
  170. continue;
  171. }
  172. origin_strings.push_back(origin);
  173. }
  174. return origin_strings;
  175. }
  176. // Combines the origin lists contained in |value1| and |value2| separated by
  177. // commas. The lists are concatenated, with invalid or duplicate origins
  178. // removed.
  179. std::string CombineAndSanitizeOriginLists(const std::string& value1,
  180. const std::string& value2) {
  181. std::set<std::string> seen_origins;
  182. std::vector<std::string> origin_vector;
  183. for (const std::string& list : {value1, value2}) {
  184. for (const std::string& origin : TokenizeOriginList(list)) {
  185. if (!base::Contains(seen_origins, origin)) {
  186. origin_vector.push_back(origin);
  187. seen_origins.insert(origin);
  188. }
  189. }
  190. }
  191. const std::string result =
  192. base::JoinString(origin_vector, kOriginListValueSeparator);
  193. CHECK(IsSafeValue(result));
  194. return result;
  195. }
  196. // Returns the sanitized combined origin list by concatenating the command line
  197. // and the pref values. Invalid or duplicate origins are dropped.
  198. std::string GetCombinedOriginListValue(const FlagsStorage& flags_storage,
  199. const base::CommandLine& command_line,
  200. const std::string& internal_entry_name,
  201. const std::string& command_line_switch) {
  202. const std::string existing_value =
  203. command_line.GetSwitchValueASCII(command_line_switch);
  204. const std::string new_value =
  205. flags_storage.GetOriginListFlag(internal_entry_name);
  206. return CombineAndSanitizeOriginLists(existing_value, new_value);
  207. }
  208. #if BUILDFLAG(IS_CHROMEOS_ASH)
  209. // ChromeOS does not call ConvertFlagsToSwitches on startup (see
  210. // ChromeFeatureListCreator::ConvertFlagsToSwitches() for details) so the
  211. // command line cannot be updated using pref values. Instead, this method
  212. // modifies it on the fly when the user makes a change.
  213. void DidModifyOriginListFlag(const FlagsStorage& flags_storage,
  214. const FeatureEntry& entry) {
  215. base::CommandLine* current_cl = base::CommandLine::ForCurrentProcess();
  216. const std::string new_value = GetCombinedOriginListValue(
  217. flags_storage, *current_cl, entry.internal_name,
  218. entry.switches.command_line_switch);
  219. // Remove the switch if it exists.
  220. base::CommandLine new_cl(current_cl->GetProgram());
  221. const base::CommandLine::SwitchMap switches = current_cl->GetSwitches();
  222. for (const auto& it : switches) {
  223. const auto& switch_name = it.first;
  224. const auto& switch_value = it.second;
  225. if (switch_name != entry.switches.command_line_switch) {
  226. if (switch_value.empty()) {
  227. new_cl.AppendSwitch(switch_name);
  228. } else {
  229. new_cl.AppendSwitchNative(switch_name, switch_value);
  230. }
  231. }
  232. }
  233. *current_cl = new_cl;
  234. const std::string sanitized =
  235. CombineAndSanitizeOriginLists(std::string(), new_value);
  236. current_cl->AppendSwitchASCII(entry.switches.command_line_switch, sanitized);
  237. }
  238. #endif
  239. } // namespace
  240. struct FlagsState::SwitchEntry {
  241. // Corresponding base::Feature to toggle.
  242. std::string feature_name;
  243. // If |feature_name| is not empty, the state (enable/disabled) to set.
  244. bool feature_state;
  245. // The name of the switch to add.
  246. std::string switch_name;
  247. // If |switch_name| is not empty, the value of the switch to set.
  248. std::string switch_value;
  249. // If |variation_id| is not empty, variation id value to set.
  250. // In the format of VariationsIdsProvider::ForceVariationIds().
  251. std::string variation_id;
  252. SwitchEntry() : feature_state(false) {}
  253. };
  254. bool FlagsState::Delegate::ShouldExcludeFlag(const FlagsStorage* state,
  255. const FeatureEntry& entry) {
  256. return false;
  257. }
  258. FlagsState::Delegate::Delegate() = default;
  259. FlagsState::Delegate::~Delegate() = default;
  260. FlagsState::FlagsState(base::span<const FeatureEntry> feature_entries,
  261. FlagsState::Delegate* delegate)
  262. : feature_entries_(feature_entries),
  263. needs_restart_(false),
  264. delegate_(delegate) {}
  265. FlagsState::~FlagsState() {}
  266. void FlagsState::ConvertFlagsToSwitches(
  267. FlagsStorage* flags_storage,
  268. base::CommandLine* command_line,
  269. SentinelsMode sentinels,
  270. const char* enable_features_flag_name,
  271. const char* disable_features_flag_name) {
  272. std::set<std::string> enabled_entries;
  273. std::map<std::string, SwitchEntry> name_to_switch_map;
  274. GenerateFlagsToSwitchesMapping(flags_storage, *command_line, &enabled_entries,
  275. &name_to_switch_map);
  276. AddSwitchesToCommandLine(enabled_entries, name_to_switch_map, sentinels,
  277. command_line, enable_features_flag_name,
  278. disable_features_flag_name);
  279. }
  280. void FlagsState::GetSwitchesAndFeaturesFromFlags(
  281. FlagsStorage* flags_storage,
  282. std::set<std::string>* switches,
  283. std::set<std::string>* features,
  284. std::set<std::string>* variation_ids) const {
  285. std::set<std::string> enabled_entries;
  286. std::map<std::string, SwitchEntry> name_to_switch_map;
  287. GenerateFlagsToSwitchesMapping(flags_storage,
  288. *base::CommandLine::ForCurrentProcess(),
  289. &enabled_entries, &name_to_switch_map);
  290. for (const std::string& entry_name : enabled_entries) {
  291. const auto& entry_it = name_to_switch_map.find(entry_name);
  292. DCHECK(entry_it != name_to_switch_map.end());
  293. const SwitchEntry& entry = entry_it->second;
  294. if (!entry.switch_name.empty())
  295. switches->insert("--" + entry.switch_name);
  296. if (!entry.feature_name.empty()) {
  297. if (entry.feature_state)
  298. features->insert(entry.feature_name + ":enabled");
  299. else
  300. features->insert(entry.feature_name + ":disabled");
  301. if (!entry.variation_id.empty()) {
  302. variation_ids->insert(entry.variation_id);
  303. }
  304. }
  305. }
  306. }
  307. bool FlagsState::IsRestartNeededToCommitChanges() {
  308. return needs_restart_;
  309. }
  310. void FlagsState::SetFeatureEntryEnabled(FlagsStorage* flags_storage,
  311. const std::string& internal_name,
  312. bool enable) {
  313. size_t at_index = internal_name.find(testing::kMultiSeparator);
  314. if (at_index != std::string::npos) {
  315. DCHECK(enable);
  316. // We're being asked to enable a multi-choice entry. Disable the
  317. // currently selected choice.
  318. DCHECK_NE(at_index, 0u);
  319. const std::string entry_name = internal_name.substr(0, at_index);
  320. SetFeatureEntryEnabled(flags_storage, entry_name, false);
  321. // And enable the new choice, if it is not the default first choice.
  322. if (internal_name != entry_name + "@0") {
  323. std::set<std::string> enabled_entries;
  324. GetSanitizedEnabledFlags(flags_storage, &enabled_entries);
  325. needs_restart_ |= enabled_entries.insert(internal_name).second;
  326. flags_storage->SetFlags(enabled_entries);
  327. }
  328. return;
  329. }
  330. std::set<std::string> enabled_entries;
  331. GetSanitizedEnabledFlags(flags_storage, &enabled_entries);
  332. const FeatureEntry* e = FindFeatureEntryByName(internal_name);
  333. DCHECK(e);
  334. if (e->type == FeatureEntry::SINGLE_VALUE ||
  335. e->type == FeatureEntry::ORIGIN_LIST_VALUE) {
  336. if (enable)
  337. needs_restart_ |= enabled_entries.insert(internal_name).second;
  338. else
  339. needs_restart_ |= (enabled_entries.erase(internal_name) > 0);
  340. #if BUILDFLAG(IS_CHROMEOS_ASH)
  341. // If an origin list was enabled or disabled, update the command line flag.
  342. if (e->type == FeatureEntry::ORIGIN_LIST_VALUE && enable)
  343. DidModifyOriginListFlag(*flags_storage, *e);
  344. #endif
  345. } else if (e->type == FeatureEntry::SINGLE_DISABLE_VALUE) {
  346. if (!enable)
  347. needs_restart_ |= enabled_entries.insert(internal_name).second;
  348. else
  349. needs_restart_ |= (enabled_entries.erase(internal_name) > 0);
  350. } else {
  351. if (enable) {
  352. // Enable the first choice.
  353. needs_restart_ |= enabled_entries.insert(e->NameForOption(0)).second;
  354. } else {
  355. // Find the currently enabled choice and disable it.
  356. for (int i = 0; i < e->NumOptions(); ++i) {
  357. std::string choice_name = e->NameForOption(i);
  358. if (enabled_entries.find(choice_name) != enabled_entries.end()) {
  359. needs_restart_ = true;
  360. enabled_entries.erase(choice_name);
  361. // Continue on just in case there's a bug and more than one
  362. // entry for this choice was enabled.
  363. }
  364. }
  365. }
  366. }
  367. flags_storage->SetFlags(enabled_entries);
  368. }
  369. void FlagsState::SetOriginListFlag(const std::string& internal_name,
  370. const std::string& value,
  371. FlagsStorage* flags_storage) {
  372. const std::string new_value =
  373. CombineAndSanitizeOriginLists(std::string(), value);
  374. flags_storage->SetOriginListFlag(internal_name, new_value);
  375. #if BUILDFLAG(IS_CHROMEOS_ASH)
  376. const FeatureEntry* entry = FindFeatureEntryByName(internal_name);
  377. DCHECK(entry);
  378. std::set<std::string> enabled_entries;
  379. GetSanitizedEnabledFlags(flags_storage, &enabled_entries);
  380. const bool enabled = base::Contains(enabled_entries, entry->internal_name);
  381. if (enabled)
  382. DidModifyOriginListFlag(*flags_storage, *entry);
  383. #endif
  384. }
  385. void FlagsState::RemoveFlagsSwitches(
  386. base::CommandLine::SwitchMap* switch_list) {
  387. for (const auto& entry : flags_switches_)
  388. switch_list->erase(entry.first);
  389. // If feature entries were added to --enable-features= or --disable-features=
  390. // lists, remove them here while preserving existing values.
  391. for (const auto& entry : appended_switches_) {
  392. const auto& switch_name = entry.first;
  393. const auto& switch_added_values = entry.second;
  394. // The below is either a std::string or a std::u16string based on platform.
  395. const auto& existing_value = (*switch_list)[switch_name];
  396. #if BUILDFLAG(IS_WIN)
  397. const std::string existing_value_utf8 = base::WideToUTF8(existing_value);
  398. #else
  399. const std::string& existing_value_utf8 = existing_value;
  400. #endif
  401. std::vector<base::StringPiece> features =
  402. base::FeatureList::SplitFeatureListString(existing_value_utf8);
  403. std::vector<base::StringPiece> remaining_features;
  404. // For any featrue name in |features| that is not in |switch_added_values| -
  405. // i.e. it wasn't added by about_flags code, add it to |remaining_features|.
  406. for (const auto& feature : features) {
  407. if (!base::Contains(switch_added_values, std::string(feature)))
  408. remaining_features.push_back(feature);
  409. }
  410. // Either remove the flag entirely if |remaining_features| is empty, or set
  411. // the new list.
  412. if (remaining_features.empty()) {
  413. switch_list->erase(switch_name);
  414. } else {
  415. std::string switch_value = base::JoinString(remaining_features, ",");
  416. #if BUILDFLAG(IS_WIN)
  417. (*switch_list)[switch_name] = base::UTF8ToWide(switch_value);
  418. #else
  419. (*switch_list)[switch_name] = switch_value;
  420. #endif
  421. }
  422. }
  423. }
  424. void FlagsState::ResetAllFlags(FlagsStorage* flags_storage) {
  425. needs_restart_ = true;
  426. std::set<std::string> no_entries;
  427. flags_storage->SetFlags(no_entries);
  428. }
  429. void FlagsState::Reset() {
  430. needs_restart_ = false;
  431. flags_switches_.clear();
  432. appended_switches_.clear();
  433. }
  434. std::vector<std::string> FlagsState::RegisterAllFeatureVariationParameters(
  435. FlagsStorage* flags_storage,
  436. base::FeatureList* feature_list) {
  437. std::set<std::string> enabled_entries;
  438. GetSanitizedEnabledFlagsForCurrentPlatform(flags_storage, &enabled_entries);
  439. return RegisterEnabledFeatureVariationParameters(
  440. feature_entries_, enabled_entries, internal::kTrialGroupAboutFlags,
  441. feature_list);
  442. }
  443. // static
  444. std::vector<std::string> FlagsState::RegisterEnabledFeatureVariationParameters(
  445. const base::span<const FeatureEntry>& feature_entries,
  446. const std::set<std::string>& enabled_entries,
  447. const std::string& trial_group,
  448. base::FeatureList* feature_list) {
  449. std::vector<std::string> variation_ids;
  450. std::map<std::string, std::set<std::string>> enabled_features_by_trial_name;
  451. std::map<std::string, std::map<std::string, std::string>>
  452. params_by_trial_name;
  453. // First collect all the data for each trial.
  454. for (const FeatureEntry& entry : feature_entries) {
  455. if (entry.type == FeatureEntry::FEATURE_WITH_PARAMS_VALUE
  456. #if BUILDFLAG(IS_CHROMEOS_ASH)
  457. || entry.type == FeatureEntry::PLATFORM_FEATURE_NAME_WITH_PARAMS_VALUE
  458. #endif // BUILDFLAG(IS_CHROMEOS_ASH)
  459. ) {
  460. for (int j = 0; j < entry.NumOptions(); ++j) {
  461. if (entry.StateForOption(j) == FeatureEntry::FeatureState::ENABLED &&
  462. enabled_entries.count(entry.NameForOption(j))) {
  463. std::string trial_name;
  464. if (entry.type == FeatureEntry::FEATURE_WITH_PARAMS_VALUE) {
  465. trial_name = entry.feature.feature_trial_name;
  466. // The user has chosen to enable the feature by this option.
  467. enabled_features_by_trial_name[trial_name].insert(
  468. entry.feature.feature->name);
  469. }
  470. #if BUILDFLAG(IS_CHROMEOS_ASH)
  471. else {
  472. trial_name = entry.platform_feature_name.feature_trial_name;
  473. // The user has chosen to enable the feature by this option.
  474. enabled_features_by_trial_name[trial_name].insert(
  475. entry.platform_feature_name.name);
  476. }
  477. #endif // BUILDFLAG(IS_CHROMEOS_ASH)
  478. const FeatureEntry::FeatureVariation* variation =
  479. entry.VariationForOption(j);
  480. if (!variation)
  481. continue;
  482. // The selected variation is non-default, collect its params & id.
  483. for (int i = 0; i < variation->num_params; ++i) {
  484. auto insert_result = params_by_trial_name[trial_name].insert(
  485. std::make_pair(variation->params[i].param_name,
  486. variation->params[i].param_value));
  487. DCHECK(insert_result.second)
  488. << "Multiple values for the same parameter '"
  489. << variation->params[i].param_name
  490. << "' are specified in chrome://flags!";
  491. }
  492. if (variation->variation_id)
  493. variation_ids.push_back(variation->variation_id);
  494. }
  495. }
  496. }
  497. }
  498. // Now create the trials and associate the features to them.
  499. for (const auto& kv : enabled_features_by_trial_name) {
  500. const std::string& trial_name = kv.first;
  501. const std::set<std::string>& trial_features = kv.second;
  502. base::FieldTrial* field_trial = RegisterFeatureVariationParameters(
  503. trial_name, params_by_trial_name[trial_name], trial_group);
  504. if (!field_trial)
  505. continue;
  506. for (const std::string& feature_name : trial_features) {
  507. feature_list->RegisterFieldTrialOverride(
  508. feature_name,
  509. base::FeatureList::OverrideState::OVERRIDE_ENABLE_FEATURE,
  510. field_trial);
  511. }
  512. }
  513. return variation_ids;
  514. }
  515. void FlagsState::GetFlagFeatureEntries(
  516. FlagsStorage* flags_storage,
  517. FlagAccess access,
  518. base::Value::List& supported_entries,
  519. base::Value::List& unsupported_entries,
  520. base::RepeatingCallback<bool(const FeatureEntry&)> skip_feature_entry) {
  521. DCHECK(flags_storage);
  522. std::set<std::string> enabled_entries;
  523. GetSanitizedEnabledFlags(flags_storage, &enabled_entries);
  524. int current_platform = GetCurrentPlatform();
  525. for (const FeatureEntry& entry : feature_entries_) {
  526. if (skip_feature_entry.Run(entry))
  527. continue;
  528. base::Value::Dict data;
  529. data.Set("internal_name", entry.internal_name);
  530. data.Set("name", entry.visible_name);
  531. data.Set("description", entry.visible_description);
  532. base::Value::List supported_platforms;
  533. AddOsStrings(entry.supported_platforms, &supported_platforms);
  534. data.Set("supported_platforms", std::move(supported_platforms));
  535. // True if the switch is not currently passed.
  536. bool is_default_value = IsDefaultValue(entry, enabled_entries);
  537. data.Set("is_default", is_default_value);
  538. switch (entry.type) {
  539. case FeatureEntry::SINGLE_VALUE:
  540. case FeatureEntry::SINGLE_DISABLE_VALUE:
  541. data.Set(
  542. "enabled",
  543. (!is_default_value && entry.type == FeatureEntry::SINGLE_VALUE) ||
  544. (is_default_value &&
  545. entry.type == FeatureEntry::SINGLE_DISABLE_VALUE));
  546. break;
  547. case FeatureEntry::ORIGIN_LIST_VALUE:
  548. data.Set("enabled", !is_default_value);
  549. data.Set("origin_list_value",
  550. GetCombinedOriginListValue(
  551. *flags_storage, *base::CommandLine::ForCurrentProcess(),
  552. entry.internal_name, entry.switches.command_line_switch));
  553. break;
  554. case FeatureEntry::MULTI_VALUE:
  555. case FeatureEntry::ENABLE_DISABLE_VALUE:
  556. case FeatureEntry::FEATURE_VALUE:
  557. case FeatureEntry::FEATURE_WITH_PARAMS_VALUE:
  558. #if BUILDFLAG(IS_CHROMEOS_ASH)
  559. case FeatureEntry::PLATFORM_FEATURE_NAME_VALUE:
  560. case FeatureEntry::PLATFORM_FEATURE_NAME_WITH_PARAMS_VALUE:
  561. #endif // BUILDFLAG(IS_CHROMEOS_ASH)
  562. data.Set("options", CreateOptionsData(entry, enabled_entries));
  563. break;
  564. }
  565. bool supported = (entry.supported_platforms & current_platform) != 0;
  566. #if BUILDFLAG(IS_CHROMEOS_ASH)
  567. if (access == kOwnerAccessToFlags &&
  568. (entry.supported_platforms & kOsCrOSOwnerOnly) != 0) {
  569. supported = true;
  570. }
  571. #if BUILDFLAG(ENABLE_BANNED_BASE_FEATURE_PREFIX)
  572. if ((entry.type == FeatureEntry::PLATFORM_FEATURE_NAME_VALUE ||
  573. entry.type == FeatureEntry::PLATFORM_FEATURE_NAME_WITH_PARAMS_VALUE) &&
  574. !base::StartsWith(entry.platform_feature_name.name,
  575. BUILDFLAG(BANNED_BASE_FEATURE_PREFIX))) {
  576. LOG(ERROR) << "mising required prefix for "
  577. << entry.platform_feature_name.name;
  578. supported = false;
  579. }
  580. #endif // BUILDFLAG(ENABLED_BANNED_BASE_FEATURE_PREFIX)
  581. #endif // BUILDFLAG(IS_CHROMEOS_ASH)
  582. if (supported)
  583. supported_entries.Append(std::move(data));
  584. else
  585. unsupported_entries.Append(std::move(data));
  586. }
  587. }
  588. // static
  589. unsigned short FlagsState::GetCurrentPlatform() {
  590. #if BUILDFLAG(IS_IOS)
  591. return kOsIos;
  592. #elif BUILDFLAG(IS_MAC)
  593. return kOsMac;
  594. #elif BUILDFLAG(IS_WIN)
  595. return kOsWin;
  596. #elif BUILDFLAG(IS_CHROMEOS_ASH)
  597. return kOsCrOS;
  598. #elif BUILDFLAG(IS_CHROMEOS_LACROS)
  599. return kOsLacros;
  600. #elif BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_OPENBSD)
  601. return kOsLinux;
  602. #elif BUILDFLAG(IS_ANDROID)
  603. return kOsAndroid;
  604. #elif BUILDFLAG(IS_FUCHSIA)
  605. return kOsFuchsia;
  606. #else
  607. #error Unknown platform
  608. #endif
  609. }
  610. void FlagsState::AddSwitchMapping(
  611. const std::string& key,
  612. const std::string& switch_name,
  613. const std::string& switch_value,
  614. std::map<std::string, SwitchEntry>* name_to_switch_map) const {
  615. DCHECK(!base::Contains(*name_to_switch_map, key));
  616. SwitchEntry* entry = &(*name_to_switch_map)[key];
  617. entry->switch_name = switch_name;
  618. entry->switch_value = switch_value;
  619. }
  620. void FlagsState::AddFeatureMapping(
  621. const std::string& key,
  622. const std::string& feature_name,
  623. bool feature_state,
  624. const std::string& variation_id,
  625. std::map<std::string, SwitchEntry>* name_to_switch_map) const {
  626. DCHECK(!base::Contains(*name_to_switch_map, key));
  627. SwitchEntry* entry = &(*name_to_switch_map)[key];
  628. entry->feature_name = feature_name;
  629. entry->feature_state = feature_state;
  630. entry->variation_id = variation_id;
  631. }
  632. void FlagsState::AddSwitchesToCommandLine(
  633. const std::set<std::string>& enabled_entries,
  634. const std::map<std::string, SwitchEntry>& name_to_switch_map,
  635. SentinelsMode sentinels,
  636. base::CommandLine* command_line,
  637. const char* enable_features_flag_name,
  638. const char* disable_features_flag_name) {
  639. std::map<std::string, bool> feature_switches;
  640. if (sentinels == kAddSentinels) {
  641. command_line->AppendSwitch(switches::kFlagSwitchesBegin);
  642. flags_switches_[switches::kFlagSwitchesBegin] = std::string();
  643. }
  644. std::vector<std::string> variation_ids;
  645. for (const std::string& entry_name : enabled_entries) {
  646. const auto& entry_it = name_to_switch_map.find(entry_name);
  647. if (entry_it == name_to_switch_map.end()) {
  648. NOTREACHED();
  649. continue;
  650. }
  651. const SwitchEntry& entry = entry_it->second;
  652. if (!entry.feature_name.empty()) {
  653. feature_switches[entry.feature_name] = entry.feature_state;
  654. if (!entry.variation_id.empty()) {
  655. variation_ids.push_back(entry.variation_id);
  656. }
  657. } else if (!entry.switch_name.empty()) {
  658. command_line->AppendSwitchASCII(entry.switch_name, entry.switch_value);
  659. flags_switches_[entry.switch_name] = entry.switch_value;
  660. }
  661. // If an entry doesn't match either of the above, then it is likely the
  662. // default entry for a FEATURE_VALUE entry. Safe to ignore.
  663. }
  664. if (!feature_switches.empty()) {
  665. MergeFeatureCommandLineSwitch(feature_switches, enable_features_flag_name,
  666. true, command_line);
  667. MergeFeatureCommandLineSwitch(feature_switches, disable_features_flag_name,
  668. false, command_line);
  669. }
  670. if (!variation_ids.empty()) {
  671. MergeVariationIdsCommandLineSwitch(variation_ids, command_line);
  672. }
  673. if (sentinels == kAddSentinels) {
  674. command_line->AppendSwitch(switches::kFlagSwitchesEnd);
  675. flags_switches_[switches::kFlagSwitchesEnd] = std::string();
  676. }
  677. }
  678. void FlagsState::MergeFeatureCommandLineSwitch(
  679. const std::map<std::string, bool>& feature_switches,
  680. const char* switch_name,
  681. bool feature_state,
  682. base::CommandLine* command_line) {
  683. std::string original_switch_value =
  684. command_line->GetSwitchValueASCII(switch_name);
  685. std::vector<base::StringPiece> features =
  686. base::FeatureList::SplitFeatureListString(original_switch_value);
  687. // Only add features that don't already exist in the lists.
  688. // Note: The base::Contains() call results in O(n^2) performance, but in
  689. // practice n should be very small.
  690. for (const auto& entry : feature_switches) {
  691. if (entry.second == feature_state &&
  692. !base::Contains(features, entry.first)) {
  693. features.push_back(entry.first);
  694. appended_switches_[switch_name].insert(entry.first);
  695. }
  696. }
  697. // Update the switch value only if it didn't change. This avoids setting an
  698. // empty list or duplicating the same list (since AppendSwitch() adds the
  699. // switch to the end but doesn't remove previous ones).
  700. std::string switch_value = base::JoinString(features, ",");
  701. if (switch_value != original_switch_value)
  702. command_line->AppendSwitchASCII(switch_name, switch_value);
  703. }
  704. void FlagsState::MergeVariationIdsCommandLineSwitch(
  705. const std::vector<std::string>& variation_ids,
  706. base::CommandLine* command_line) {
  707. DCHECK(!variation_ids.empty());
  708. std::string variation_ids_switch = command_line->GetSwitchValueASCII(
  709. variations::switches::kForceVariationIds);
  710. // At this point, the switch value is guaranteed to change since
  711. // |variation_ids| is not empty. Hence, we do not conditionally update the
  712. // switch value, as is done in FlagsState::MergeFeatureCommandLineSwitch().
  713. // Note that it is an error to try to set the same variation id in multiple
  714. // ways.
  715. command_line->AppendSwitchASCII(
  716. variations::switches::kForceVariationIds,
  717. base::StrCat({variation_ids_switch,
  718. variation_ids_switch.empty() ? "" : ",",
  719. base::JoinString(variation_ids, ",")}));
  720. }
  721. std::set<std::string> FlagsState::SanitizeList(
  722. const FlagsStorage* storage,
  723. const std::set<std::string>& enabled_entries,
  724. int platform_mask) const {
  725. std::set<std::string> new_enabled_entries;
  726. // For each entry in |enabled_entries|, check whether it exists in the list
  727. // of supported features. Remove those that don't. Note: Even though this is
  728. // an O(n^2) search, this is more efficient than creating a set from
  729. // |feature_entries_| first because |feature_entries_| is large and
  730. // |enabled_entries| should generally be small/empty.
  731. for (const std::string& entry_name : enabled_entries) {
  732. if (IsSupportedFeature(storage, entry_name, platform_mask))
  733. new_enabled_entries.insert(entry_name);
  734. }
  735. return new_enabled_entries;
  736. }
  737. void FlagsState::GetSanitizedEnabledFlags(FlagsStorage* flags_storage,
  738. std::set<std::string>* result) const {
  739. std::set<std::string> enabled_entries = flags_storage->GetFlags();
  740. std::set<std::string> new_enabled_entries =
  741. SanitizeList(flags_storage, enabled_entries, -1);
  742. if (new_enabled_entries.size() != enabled_entries.size())
  743. flags_storage->SetFlags(new_enabled_entries);
  744. result->swap(new_enabled_entries);
  745. }
  746. void FlagsState::GetSanitizedEnabledFlagsForCurrentPlatform(
  747. FlagsStorage* flags_storage,
  748. std::set<std::string>* result) const {
  749. // TODO(asvitkine): Consider making GetSanitizedEnabledFlags() do the platform
  750. // filtering by default so that we don't need two calls to SanitizeList().
  751. GetSanitizedEnabledFlags(flags_storage, result);
  752. int platform_mask = GetCurrentPlatform();
  753. #if BUILDFLAG(IS_CHROMEOS_ASH)
  754. platform_mask |= kOsCrOSOwnerOnly;
  755. #endif
  756. std::set<std::string> platform_entries =
  757. SanitizeList(flags_storage, *result, platform_mask);
  758. result->swap(platform_entries);
  759. }
  760. void FlagsState::GenerateFlagsToSwitchesMapping(
  761. FlagsStorage* flags_storage,
  762. const base::CommandLine& command_line,
  763. std::set<std::string>* enabled_entries,
  764. std::map<std::string, SwitchEntry>* name_to_switch_map) const {
  765. GetSanitizedEnabledFlagsForCurrentPlatform(flags_storage, enabled_entries);
  766. if (enabled_entries->empty())
  767. return;
  768. for (const FeatureEntry& entry : feature_entries_) {
  769. switch (entry.type) {
  770. case FeatureEntry::SINGLE_VALUE:
  771. case FeatureEntry::SINGLE_DISABLE_VALUE:
  772. AddSwitchMapping(entry.internal_name,
  773. entry.switches.command_line_switch,
  774. entry.switches.command_line_value, name_to_switch_map);
  775. break;
  776. case FeatureEntry::ORIGIN_LIST_VALUE: {
  777. // Combine the existing command line value with the user provided list.
  778. // This is done to retain the existing list from the command line when
  779. // the browser is restarted. Otherwise, the user provided list would
  780. // overwrite the list provided from the command line.
  781. const std::string origin_list_value = GetCombinedOriginListValue(
  782. *flags_storage, command_line, entry.internal_name,
  783. entry.switches.command_line_switch);
  784. AddSwitchMapping(entry.internal_name,
  785. entry.switches.command_line_switch, origin_list_value,
  786. name_to_switch_map);
  787. break;
  788. }
  789. case FeatureEntry::MULTI_VALUE:
  790. for (int j = 0; j < entry.NumOptions(); ++j) {
  791. AddSwitchMapping(entry.NameForOption(j),
  792. entry.ChoiceForOption(j).command_line_switch,
  793. entry.ChoiceForOption(j).command_line_value,
  794. name_to_switch_map);
  795. }
  796. break;
  797. case FeatureEntry::ENABLE_DISABLE_VALUE:
  798. AddSwitchMapping(entry.NameForOption(0), std::string(), std::string(),
  799. name_to_switch_map);
  800. AddSwitchMapping(entry.NameForOption(1),
  801. entry.switches.command_line_switch,
  802. entry.switches.command_line_value, name_to_switch_map);
  803. AddSwitchMapping(
  804. entry.NameForOption(2), entry.switches.disable_command_line_switch,
  805. entry.switches.disable_command_line_value, name_to_switch_map);
  806. break;
  807. case FeatureEntry::FEATURE_VALUE:
  808. case FeatureEntry::FEATURE_WITH_PARAMS_VALUE:
  809. #if BUILDFLAG(IS_CHROMEOS_ASH)
  810. case FeatureEntry::PLATFORM_FEATURE_NAME_VALUE:
  811. case FeatureEntry::PLATFORM_FEATURE_NAME_WITH_PARAMS_VALUE:
  812. #endif // BUILDFLAG(IS_CHROMEOS_ASH)
  813. for (int j = 0; j < entry.NumOptions(); ++j) {
  814. FeatureEntry::FeatureState state = entry.StateForOption(j);
  815. if (state == FeatureEntry::FeatureState::DEFAULT) {
  816. AddFeatureMapping(entry.NameForOption(j), std::string(), false,
  817. std::string(), name_to_switch_map);
  818. } else {
  819. const FeatureEntry::FeatureVariation* variation =
  820. entry.VariationForOption(j);
  821. std::string feature_name;
  822. if (entry.type == FeatureEntry::FEATURE_VALUE ||
  823. entry.type == FeatureEntry::FEATURE_WITH_PARAMS_VALUE) {
  824. feature_name = entry.feature.feature->name;
  825. }
  826. #if BUILDFLAG(IS_CHROMEOS_ASH)
  827. else {
  828. feature_name = entry.platform_feature_name.name;
  829. }
  830. #endif // BUILDFLAG(IS_CHROMEOS_ASH)
  831. std::vector<std::string> params_value;
  832. std::string variation_id;
  833. if (variation) {
  834. feature_name.append(":");
  835. for (int i = 0; i < variation->num_params; ++i) {
  836. std::string param_name =
  837. variations::EscapeValue(variation->params[i].param_name);
  838. std::string param_value =
  839. variations::EscapeValue(variation->params[i].param_value);
  840. params_value.push_back(
  841. param_name.append("/").append(param_value));
  842. }
  843. if (variation->variation_id) {
  844. variation_id = variation->variation_id;
  845. }
  846. }
  847. AddFeatureMapping(
  848. entry.NameForOption(j),
  849. feature_name.append(base::JoinString(params_value, "/")),
  850. state == FeatureEntry::FeatureState::ENABLED, variation_id,
  851. name_to_switch_map);
  852. }
  853. }
  854. break;
  855. }
  856. }
  857. }
  858. const FeatureEntry* FlagsState::FindFeatureEntryByName(
  859. const std::string& internal_name) const {
  860. for (const FeatureEntry& entry : feature_entries_) {
  861. if (entry.internal_name == internal_name)
  862. return &entry;
  863. }
  864. return nullptr;
  865. }
  866. bool FlagsState::IsSupportedFeature(const FlagsStorage* storage,
  867. const std::string& name,
  868. int platform_mask) const {
  869. for (const auto& entry : feature_entries_) {
  870. DCHECK(entry.IsValid());
  871. if (!(entry.supported_platforms & platform_mask))
  872. continue;
  873. if (!entry.InternalNameMatches(name))
  874. continue;
  875. if (delegate_ && delegate_->ShouldExcludeFlag(storage, entry))
  876. continue;
  877. return true;
  878. }
  879. return false;
  880. }
  881. } // namespace flags_ui