Add support to chrome://flags for command line flags that need to be treated as a list of origins
This CL adds support for command line flags that are lists of url::Origins. E.g. The value in --flag=value will now be modifyable from the chrome://flags page using a free form textbox, and will be treated as a comma separated list of origins (e.g. http://example1.test,http://example2.test)
The string from the textbox is transformed as follows:
- Continuous whitespace characters are collapsed into single a space
- String is tokenized using space and comma as delimiters
- Each token is parsed as a GURL. Invalid URLs or URLs with a scheme other than http and https are discarded.
- Remaining URLs are converted to url::Origins, then joined into a single, comma separated string.
The CL also adds --unsafely-treat-insecure-origin-as-secure as the first such flag to chrome://flags. Developers will now be able to modify the list of insecure origins treated as secure from the chrome://flags page on all platforms.
Bug: 834381
Change-Id: Iad44b5b2724687c7bea1ae45c23ccc910eb5cc9f
Reviewed-on: https://chromium-review.googlesource.com/1038152
Reviewed-by: Lei Zhang <[email protected]>
Reviewed-by: Mattias Nissler <[email protected]>
Reviewed-by: Mike West <[email protected]>
Reviewed-by: Edward Jung <[email protected]>
Reviewed-by: Alexei Svitkine <[email protected]>
Commit-Queue: Mustafa Emre Acer <[email protected]>
Cr-Commit-Position: refs/heads/master@{#560791}
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index c6360dd5..326032de 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -3885,6 +3885,11 @@
flag_descriptions::kWebSocketHandshakeReuseConnectionDescription, kOsAll,
FEATURE_VALUE_TYPE(net::WebSocketBasicHandshakeStream::
kWebSocketHandshakeReuseConnection)},
+ {"unsafely-treat-insecure-origin-as-secure",
+ flag_descriptions::kTreatInsecureOriginAsSecureName,
+ flag_descriptions::kTreatInsecureOriginAsSecureDescription, kOsAll,
+ ORIGIN_LIST_VALUE_TYPE(switches::kUnsafelyTreatInsecureOriginAsSecure,
+ "")},
// NOTE: Adding a new flag requires adding a corresponding entry to enum
// "LoginCustomFlags" in tools/metrics/histograms/enums.xml. See "Flag
@@ -4052,6 +4057,13 @@
flags_storage, internal_name, enable);
}
+void SetOriginListFlag(const std::string& internal_name,
+ const std::string& value,
+ flags_ui::FlagsStorage* flags_storage) {
+ FlagsStateSingleton::GetFlagsState()->SetOriginListFlag(internal_name, value,
+ flags_storage);
+}
+
void RemoveFlagsSwitches(base::CommandLine::SwitchMap* switch_list) {
FlagsStateSingleton::GetFlagsState()->RemoveFlagsSwitches(switch_list);
}
diff --git a/chrome/browser/about_flags.h b/chrome/browser/about_flags.h
index 9714dea8..199f571 100644
--- a/chrome/browser/about_flags.h
+++ b/chrome/browser/about_flags.h
@@ -72,6 +72,17 @@
const std::string& internal_name,
bool enable);
+// Sets a flag value with a list of origins given by |value|. Origins in |value|
+// can be separated by a comma or whitespace. Invalid URLs will be dropped when
+// setting the command line flag.
+// E.g. SetOriginListFlag("test-flag",
+// "http://example.test1 http://example.test2",
+// flags_storage);
+// will add --test-flag=http://example.test to the command line.
+void SetOriginListFlag(const std::string& internal_name,
+ const std::string& value,
+ flags_ui::FlagsStorage* flags_storage);
+
// Removes all switches that were added to a command line by a previous call to
// |ConvertFlagsToSwitches()|.
void RemoveFlagsSwitches(base::CommandLine::SwitchMap* switch_list);
diff --git a/chrome/browser/about_flags_browsertest.cc b/chrome/browser/about_flags_browsertest.cc
new file mode 100644
index 0000000..29197cf
--- /dev/null
+++ b/chrome/browser/about_flags_browsertest.cc
@@ -0,0 +1,141 @@
+// Copyright 2018 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 "base/command_line.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "build/build_config.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/interactive_test_utils.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/test/browser_test_utils.h"
+#include "ui/base/window_open_disposition.h"
+
+namespace {
+
+const char kSwitchName[] = "unsafely-treat-insecure-origin-as-secure";
+
+void SimulateTextType(content::WebContents* contents,
+ const char* experiment_id,
+ const char* text) {
+ EXPECT_TRUE(content::ExecuteScript(
+ contents, base::StringPrintf(
+ "var parent = document.getElementById('%s');"
+ "var textarea = parent.getElementsByTagName('textarea')[0];"
+ "textarea.focus();"
+ "textarea.value = `%s`;"
+ "textarea.onchange();",
+ experiment_id, text)));
+}
+
+void ToggleEnableDropdown(content::WebContents* contents,
+ const char* experiment_id,
+ bool enable) {
+ EXPECT_TRUE(content::ExecuteScript(
+ contents,
+ base::StringPrintf(
+ "var k = "
+ "document.getElementById('%s');"
+ "var s = k.getElementsByClassName('experiment-enable-disable')[0];"
+ "s.focus();"
+ "s.selectedIndex = %d;"
+ "s.onchange();",
+ experiment_id, enable ? 1 : 0)));
+}
+
+void SetSwitch(base::CommandLine::SwitchMap* switch_map,
+ const std::string& switch_name,
+ const std::string& switch_value) {
+#if defined(OS_WIN)
+ (*switch_map)[switch_name] = base::ASCIIToUTF16(switch_value.c_str());
+#else
+ (*switch_map)[switch_name] = switch_value;
+#endif
+}
+
+class AboutFlagsBrowserTest : public InProcessBrowserTest {};
+
+// Tests experiments with origin values in chrome://flags page.
+IN_PROC_BROWSER_TEST_F(AboutFlagsBrowserTest, OriginFlag) {
+ ui_test_utils::NavigateToURL(browser(), GURL("chrome://flags"));
+
+ const base::CommandLine::SwitchMap switches =
+ base::CommandLine::ForCurrentProcess()->GetSwitches();
+
+ content::WebContents* contents =
+ browser()->tab_strip_model()->GetActiveWebContents();
+
+ // Type a value in the experiment's textarea. Since the flag state is
+ // "Disabled" by default, command line shouldn't change.
+ SimulateTextType(contents, kSwitchName, "http://example.test");
+ EXPECT_EQ(switches, base::CommandLine::ForCurrentProcess()->GetSwitches());
+
+ // Enable the experiment. Command line should change.
+ ToggleEnableDropdown(contents, kSwitchName, true);
+ base::CommandLine::SwitchMap expected_switches = switches;
+ SetSwitch(&expected_switches, kSwitchName, "http://example.test");
+ EXPECT_EQ(expected_switches,
+ base::CommandLine::ForCurrentProcess()->GetSwitches());
+
+ // Typing while enabled should immediately change the flag.
+ SimulateTextType(contents, kSwitchName, "http://example.test.com");
+ SetSwitch(&expected_switches, kSwitchName, "http://example.test.com");
+ EXPECT_EQ(expected_switches,
+ base::CommandLine::ForCurrentProcess()->GetSwitches());
+
+ // Disable the experiment. Command line switch should be cleared.
+ ToggleEnableDropdown(contents, kSwitchName, false);
+ expected_switches.erase(kSwitchName);
+ EXPECT_EQ(expected_switches,
+ base::CommandLine::ForCurrentProcess()->GetSwitches());
+
+ // Enable again. Command line switch should be added back.
+ ToggleEnableDropdown(contents, kSwitchName, true);
+ SetSwitch(&expected_switches, kSwitchName, "http://example.test.com");
+ EXPECT_EQ(expected_switches,
+ base::CommandLine::ForCurrentProcess()->GetSwitches());
+
+ // Disable again and type. Command line switch should stay cleared.
+ ToggleEnableDropdown(contents, kSwitchName, false);
+ SimulateTextType(contents, kSwitchName, "http://example.test2.com");
+ expected_switches.erase(kSwitchName);
+ EXPECT_EQ(expected_switches,
+ base::CommandLine::ForCurrentProcess()->GetSwitches());
+
+ // Enable one last time. Command line should pick up the last typed value.
+ ToggleEnableDropdown(contents, kSwitchName, true);
+ SetSwitch(&expected_switches, kSwitchName, "http://example.test2.com");
+ EXPECT_EQ(expected_switches,
+ base::CommandLine::ForCurrentProcess()->GetSwitches());
+}
+
+// Tests that only valid http and https origins should be added to the command
+// line when modified from chrome://flags.
+IN_PROC_BROWSER_TEST_F(AboutFlagsBrowserTest, StringFlag) {
+ ui_test_utils::NavigateToURL(browser(), GURL("chrome://flags"));
+
+ const base::CommandLine::SwitchMap switches =
+ base::CommandLine::ForCurrentProcess()->GetSwitches();
+
+ content::WebContents* contents =
+ browser()->tab_strip_model()->GetActiveWebContents();
+
+ const char kValue[] =
+ "http://example.test/path http://example2.test/?query\n"
+ "invalid-value, filesystem:http://example.test.file, "
+ "ws://example3.test http://&^.com";
+
+ ToggleEnableDropdown(contents, kSwitchName, true);
+ SimulateTextType(contents, kSwitchName, kValue);
+ base::CommandLine::SwitchMap expected_switches = switches;
+ SetSwitch(&expected_switches, kSwitchName,
+ "http://example.test,http://example2.test,ws://example3.test");
+ EXPECT_EQ(expected_switches,
+ base::CommandLine::ForCurrentProcess()->GetSwitches());
+}
+
+} // namespace
diff --git a/chrome/browser/about_flags_unittest.cc b/chrome/browser/about_flags_unittest.cc
index e9fbe31..77d24923 100644
--- a/chrome/browser/about_flags_unittest.cc
+++ b/chrome/browser/about_flags_unittest.cc
@@ -182,6 +182,9 @@
case flags_ui::FeatureEntry::SINGLE_DISABLE_VALUE:
result.insert(entry.command_line_switch);
break;
+ case flags_ui::FeatureEntry::ORIGIN_LIST_VALUE:
+ // Do nothing, origin list values are not added as feature flags.
+ break;
case flags_ui::FeatureEntry::MULTI_VALUE:
for (int j = 0; j < entry.num_options; ++j) {
result.insert(entry.ChoiceForOption(j).command_line_switch);
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index d816885..a52329b 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -1708,6 +1708,14 @@
"Improved Translate UI triggering logic. TranslateRanker decides whether "
"or not Translate UI should be triggered in a given context.";
+const char kTreatInsecureOriginAsSecureName[] =
+ "Insecure origins treated as secure";
+const char kTreatInsecureOriginAsSecureDescription[] =
+ "Treat given (insecure) origins as secure origins. Multiple origins can be "
+ "supplied as a comma-separated list. For the definition of secure "
+ "contexts, "
+ "see https://w3c.github.io/webappsec-secure-contexts/";
+
const char kTrySupportedChannelLayoutsName[] =
"Causes audio output streams to check if channel layouts other than the "
"default hardware layout are available.";
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 82e23c59..e264c03 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -1043,6 +1043,9 @@
extern const char kTranslateRankerEnforcementName[];
extern const char kTranslateRankerEnforcementDescription[];
+extern const char kTreatInsecureOriginAsSecureName[];
+extern const char kTreatInsecureOriginAsSecureDescription[];
+
extern const char kTrySupportedChannelLayoutsName[];
extern const char kTrySupportedChannelLayoutsDescription[];
diff --git a/chrome/browser/ui/webui/flags_ui.cc b/chrome/browser/ui/webui/flags_ui.cc
index 7bba630..eca2c98 100644
--- a/chrome/browser/ui/webui/flags_ui.cc
+++ b/chrome/browser/ui/webui/flags_ui.cc
@@ -113,6 +113,9 @@
// Callback for the "enableExperimentalFeature" message.
void HandleEnableExperimentalFeatureMessage(const base::ListValue* args);
+ // Callback for the "setOriginListFlag" message.
+ void HandleSetOriginListFlagMessage(const base::ListValue* args);
+
// Callback for the "restartBrowser" message. Restores all tabs on restart.
void HandleRestartBrowser(const base::ListValue* args);
@@ -138,6 +141,10 @@
&FlagsDOMHandler::HandleEnableExperimentalFeatureMessage,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
+ flags_ui::kSetOriginListFlag,
+ base::BindRepeating(&FlagsDOMHandler::HandleSetOriginListFlagMessage,
+ base::Unretained(this)));
+ web_ui()->RegisterMessageCallback(
flags_ui::kRestartBrowser,
base::BindRepeating(&FlagsDOMHandler::HandleRestartBrowser,
base::Unretained(this)));
@@ -210,6 +217,26 @@
enable_str == "true");
}
+void FlagsDOMHandler::HandleSetOriginListFlagMessage(
+ const base::ListValue* args) {
+ DCHECK(flags_storage_);
+ if (args->GetSize() != 2) {
+ NOTREACHED();
+ return;
+ }
+
+ std::string entry_internal_name;
+ std::string value_str;
+ if (!args->GetString(0, &entry_internal_name) ||
+ !args->GetString(1, &value_str) || entry_internal_name.empty()) {
+ NOTREACHED();
+ return;
+ }
+
+ about_flags::SetOriginListFlag(entry_internal_name, value_str,
+ flags_storage_.get());
+}
+
void FlagsDOMHandler::HandleRestartBrowser(const base::ListValue* args) {
DCHECK(flags_storage_);
#if defined(OS_CHROMEOS)
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 317349ee..698aa1e3 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -4583,6 +4583,7 @@
test("interactive_ui_tests") {
sources = [
+ "../browser/about_flags_browsertest.cc",
"../browser/apps/app_browsertest_util.cc",
"../browser/apps/app_browsertest_util.h",
"../browser/apps/app_pointer_lock_interactive_uitest.cc",
diff --git a/components/flags_ui/BUILD.gn b/components/flags_ui/BUILD.gn
index 9d085a1..e554054 100644
--- a/components/flags_ui/BUILD.gn
+++ b/components/flags_ui/BUILD.gn
@@ -26,6 +26,7 @@
"//components/strings",
"//components/variations",
"//ui/base",
+ "//url",
]
}
diff --git a/components/flags_ui/DEPS b/components/flags_ui/DEPS
index ad5532c..89103e5 100644
--- a/components/flags_ui/DEPS
+++ b/components/flags_ui/DEPS
@@ -4,4 +4,5 @@
"+components/strings/grit/components_strings.h",
"+components/variations",
"+ui/base",
+ "+url",
]
diff --git a/components/flags_ui/feature_entry.h b/components/flags_ui/feature_entry.h
index 073ecea..15a2610 100644
--- a/components/flags_ui/feature_entry.h
+++ b/components/flags_ui/feature_entry.h
@@ -73,6 +73,10 @@
// feature is overriden to be enabled and empty set of parameters is used
// boiling down to the default behavior in the code.
FEATURE_WITH_PARAMS_VALUE,
+
+ // Corresponds to a command line switch where the value is treatead as a
+ // list of url::Origins. Default state is disabled like SINGLE_VALUE.
+ ORIGIN_LIST_VALUE
};
// Describes state of a feature.
diff --git a/components/flags_ui/feature_entry_macros.h b/components/flags_ui/feature_entry_macros.h
index e474374..3d52071 100644
--- a/components/flags_ui/feature_entry_macros.h
+++ b/components/flags_ui/feature_entry_macros.h
@@ -15,6 +15,9 @@
nullptr, nullptr, nullptr, 0, nullptr, nullptr, nullptr
#define SINGLE_VALUE_TYPE(command_line_switch) \
SINGLE_VALUE_TYPE_AND_VALUE(command_line_switch, "")
+#define ORIGIN_LIST_VALUE_TYPE(command_line_switch, switch_value) \
+ flags_ui::FeatureEntry::ORIGIN_LIST_VALUE, command_line_switch, \
+ switch_value, nullptr, nullptr, nullptr, 0, nullptr, nullptr, nullptr
#define SINGLE_DISABLE_VALUE_TYPE_AND_VALUE(command_line_switch, switch_value) \
flags_ui::FeatureEntry::SINGLE_DISABLE_VALUE, command_line_switch, \
switch_value, nullptr, nullptr, nullptr, 0, nullptr, nullptr, nullptr
diff --git a/components/flags_ui/flags_state.cc b/components/flags_ui/flags_state.cc
index b28bd45..7218bfd 100644
--- a/components/flags_ui/flags_state.cc
+++ b/components/flags_ui/flags_state.cc
@@ -16,6 +16,7 @@
#include "base/metrics/field_trial.h"
#include "base/stl_util.h"
#include "base/strings/string_piece.h"
+#include "base/strings/string_tokenizer.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
@@ -25,6 +26,8 @@
#include "components/flags_ui/flags_ui_switches.h"
#include "components/variations/variations_associated_data.h"
#include "ui/base/l10n/l10n_util.h"
+#include "url/gurl.h"
+#include "url/origin.h"
namespace flags_ui {
@@ -116,6 +119,7 @@
switch (e.type) {
case FeatureEntry::SINGLE_VALUE:
case FeatureEntry::SINGLE_DISABLE_VALUE:
+ case FeatureEntry::ORIGIN_LIST_VALUE:
names->insert(e.internal_name);
break;
case FeatureEntry::MULTI_VALUE:
@@ -134,6 +138,7 @@
switch (e.type) {
case FeatureEntry::SINGLE_VALUE:
case FeatureEntry::SINGLE_DISABLE_VALUE:
+ case FeatureEntry::ORIGIN_LIST_VALUE:
DCHECK_EQ(0, e.num_options);
DCHECK(!e.choices);
return true;
@@ -174,6 +179,7 @@
switch (entry.type) {
case FeatureEntry::SINGLE_VALUE:
case FeatureEntry::SINGLE_DISABLE_VALUE:
+ case FeatureEntry::ORIGIN_LIST_VALUE:
return enabled_entries.count(entry.internal_name) == 0;
case FeatureEntry::MULTI_VALUE:
case FeatureEntry::ENABLE_DISABLE_VALUE:
@@ -235,6 +241,51 @@
return trial;
}
+// Returns true if |value| is safe to include in a command line string in the
+// form of --flag=value.
+bool IsSafeValue(const std::string& value) {
+ // Punctuation characters at the end ("-", ".", ":", "/") are allowed because
+ // origins can contain those (e.g. http://example.test). Comma is allowed
+ // because it's used as the separator character.
+ static const char kAllowedChars[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz"
+ "0123456789"
+ "-.:/,";
+ return value.find_first_not_of(kAllowedChars) == std::string::npos;
+}
+
+// Sanitizes |value| which contains a list of origins separated by whitespace
+// and/or comma. The sanitized value is added as a command line argument, so
+// this is a security critical operation: The sanitized value must have no
+// whitespaces, each individual origin must be separated by a comma, and each
+// origin must represent a url::Origin().
+std::string SanitizeOriginListFlag(const std::string& value) {
+ const std::string input = base::CollapseWhitespaceASCII(value, false);
+ const std::string delimiters = " ,";
+ base::StringTokenizer tokenizer(input, delimiters);
+ std::vector<std::string> origin_strings;
+ while (tokenizer.GetNext()) {
+ const std::string token = tokenizer.token();
+ if (token.empty()) {
+ continue;
+ }
+ const GURL url(token);
+ if (!url.is_valid() ||
+ (!url.SchemeIsHTTPOrHTTPS() && !url.SchemeIsWSOrWSS())) {
+ continue;
+ }
+ const std::string origin = url::Origin::Create(url).Serialize();
+ if (!IsSafeValue(origin)) {
+ continue;
+ }
+ origin_strings.push_back(origin);
+ }
+ const std::string result = base::JoinString(origin_strings, ",");
+ CHECK(IsSafeValue(result));
+ return result;
+}
+
} // namespace
struct FlagsState::SwitchEntry {
@@ -347,20 +398,20 @@
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;
- }
- }
+ const FeatureEntry* e = FindFeatureEntryByName(internal_name);
DCHECK(e);
- if (e->type == FeatureEntry::SINGLE_VALUE) {
+ if (e->type == FeatureEntry::SINGLE_VALUE ||
+ e->type == FeatureEntry::ORIGIN_LIST_VALUE) {
if (enable)
needs_restart_ |= enabled_entries.insert(internal_name).second;
else
needs_restart_ |= (enabled_entries.erase(internal_name) > 0);
+
+ // If an origin list was enabled or disabled, update the command line flag.
+ if (e->type == FeatureEntry::ORIGIN_LIST_VALUE)
+ DidModifyOriginListFlag(*e, enable);
+
} else if (e->type == FeatureEntry::SINGLE_DISABLE_VALUE) {
if (!enable)
needs_restart_ |= enabled_entries.insert(internal_name).second;
@@ -387,6 +438,21 @@
flags_storage->SetFlags(enabled_entries);
}
+void FlagsState::SetOriginListFlag(const std::string& internal_name,
+ const std::string& value,
+ FlagsStorage* flags_storage) {
+ const FeatureEntry* entry = FindFeatureEntryByName(internal_name);
+ DCHECK(entry);
+
+ std::set<std::string> enabled_entries;
+ GetSanitizedEnabledFlags(flags_storage, &enabled_entries);
+ // const bool enabled =
+ // enabled_entries.find(entry->internal_name) != enabled_entries.end();
+ const bool enabled = base::ContainsKey(enabled_entries, entry->internal_name);
+ switch_values_[entry->command_line_switch] = value;
+ DidModifyOriginListFlag(*entry, enabled);
+}
+
void FlagsState::RemoveFlagsSwitches(
base::CommandLine::SwitchMap* switch_list) {
for (const auto& entry : flags_switches_)
@@ -547,6 +613,14 @@
(is_default_value &&
entry.type == FeatureEntry::SINGLE_DISABLE_VALUE));
break;
+ case FeatureEntry::ORIGIN_LIST_VALUE:
+ data->SetBoolean("enabled", !is_default_value);
+ switch_values_[entry.internal_name] =
+ base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+ entry.command_line_switch);
+ data->SetString("origin_list_value",
+ switch_values_[entry.internal_name]);
+ break;
case FeatureEntry::MULTI_VALUE:
case FeatureEntry::ENABLE_DISABLE_VALUE:
case FeatureEntry::FEATURE_VALUE:
@@ -665,6 +739,14 @@
continue;
}
+ const FeatureEntry* feature_entry = FindFeatureEntryByName(entry_name);
+ if (feature_entry &&
+ feature_entry->type == FeatureEntry::ORIGIN_LIST_VALUE) {
+ // This is not a feature value that can be enabled/disabled, it's a
+ // command line argument that takes a list of origins. Skip it.
+ continue;
+ }
+
const SwitchEntry& entry = entry_it->second;
if (!entry.feature_name.empty()) {
feature_switches[entry.feature_name] = entry.feature_state;
@@ -780,6 +862,16 @@
AddSwitchMapping(e.internal_name, e.command_line_switch,
e.command_line_value, name_to_switch_map);
break;
+
+ case FeatureEntry::ORIGIN_LIST_VALUE: {
+ const std::string value =
+ base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+ e.command_line_switch);
+ AddSwitchMapping(e.internal_name, e.command_line_switch, value,
+ name_to_switch_map);
+ break;
+ }
+
case FeatureEntry::MULTI_VALUE:
for (int j = 0; j < e.num_options; ++j) {
AddSwitchMapping(
@@ -787,6 +879,7 @@
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);
@@ -795,6 +888,7 @@
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) {
@@ -813,4 +907,40 @@
}
}
+void FlagsState::DidModifyOriginListFlag(const FeatureEntry& entry,
+ bool enabled) {
+ // Remove the switch if it exists.
+ base::CommandLine* current_cl = base::CommandLine::ForCurrentProcess();
+ base::CommandLine new_cl(current_cl->GetProgram());
+ const base::CommandLine::SwitchMap switches = current_cl->GetSwitches();
+ for (const auto& it : switches) {
+ const auto& switch_name = it.first;
+ const auto& switch_value = it.second;
+ if (switch_name != entry.command_line_switch) {
+ if (switch_value.empty()) {
+ new_cl.AppendSwitch(switch_name);
+ } else {
+ new_cl.AppendSwitchNative(switch_name, switch_value);
+ }
+ }
+ }
+ *current_cl = new_cl;
+
+ if (enabled) {
+ current_cl->AppendSwitchASCII(
+ entry.command_line_switch,
+ SanitizeOriginListFlag(switch_values_[entry.command_line_switch]));
+ }
+}
+
+const FeatureEntry* FlagsState::FindFeatureEntryByName(
+ const std::string& internal_name) const {
+ for (size_t i = 0; i < num_feature_entries_; ++i) {
+ if (feature_entries_[i].internal_name == internal_name) {
+ return feature_entries_ + i;
+ }
+ }
+ return nullptr;
+}
+
} // namespace flags_ui
diff --git a/components/flags_ui/flags_state.h b/components/flags_ui/flags_state.h
index 8540ce8..0ec9b06 100644
--- a/components/flags_ui/flags_state.h
+++ b/components/flags_ui/flags_state.h
@@ -84,6 +84,15 @@
void SetFeatureEntryEnabled(FlagsStorage* flags_storage,
const std::string& internal_name,
bool enable);
+
+ // Sets |value| as the command line switch for feature given by
+ // |internal_name|. |value| contains a list of origins (serialized form of
+ // url::Origin()) separated by whitespace and/or comma. Invalid values in this
+ // list are ignored.
+ void SetOriginListFlag(const std::string& internal_name,
+ const std::string& value,
+ FlagsStorage* flags_storage);
+
void RemoveFlagsSwitches(base::CommandLine::SwitchMap* switch_list);
void ResetAllFlags(FlagsStorage* flags_storage);
void Reset();
@@ -190,6 +199,16 @@
std::set<std::string>* enabled_entries,
std::map<std::string, SwitchEntry>* name_to_switch_map);
+ // Called when the value of an entry with ORIGIN_LIST_VALUE is modified.
+ // Modifies the corresponding command line by adding or removing the switch
+ // based on the value of |enabled|.
+ void DidModifyOriginListFlag(const FeatureEntry& entry, bool enabled);
+
+ // Returns the FeatureEntry named |internal_name|. Returns null if no entry is
+ // matched.
+ const FeatureEntry* FindFeatureEntryByName(
+ const std::string& internal_name) const;
+
const FeatureEntry* feature_entries_;
size_t num_feature_entries_;
@@ -200,6 +219,10 @@
// were appended to existing (list value) switches.
std::map<std::string, std::set<std::string>> appended_switches_;
+ // Map from switch name to switch value. Only filled for features with
+ // ORIGIN_LIST_VALUE type.
+ std::map<std::string, std::string> switch_values_;
+
DISALLOW_COPY_AND_ASSIGN(FlagsState);
};
diff --git a/components/flags_ui/flags_state_unittest.cc b/components/flags_ui/flags_state_unittest.cc
index 9524d90..17a8a9e 100644
--- a/components/flags_ui/flags_state_unittest.cc
+++ b/components/flags_ui/flags_state_unittest.cc
@@ -46,6 +46,7 @@
const char kFlags8[] = "flag8";
const char kFlags9[] = "flag9";
const char kFlags10[] = "flag10";
+const char kFlags11[] = "flag11";
const char kSwitch1[] = "switch";
const char kSwitch2[] = "switch2";
@@ -53,6 +54,9 @@
const char kSwitch6[] = "switch6";
const char kValueForSwitch2[] = "value_for_switch2";
+const char kStringSwitch[] = "string_switch";
+const char kValueForStringSwitch[] = "value_for_string_switch";
+
const char kMultiSwitch1[] = "multi_switch1";
const char kMultiSwitch2[] = "multi_switch2";
const char kValueForMultiSwitch2[] = "value_for_multi_switch2";
@@ -143,7 +147,10 @@
0, // Ends up being mapped to the current platform.
FeatureEntry::FEATURE_WITH_PARAMS_VALUE, nullptr, nullptr, nullptr,
nullptr, &kTestFeature2, 4, nullptr, kTestVariations2, kTestTrial},
-};
+ {kFlags11, kDummyName, kDummyDescription,
+ 0, // Ends up being mapped to the current platform.
+ FeatureEntry::ORIGIN_LIST_VALUE, kStringSwitch, kValueForStringSwitch,
+ nullptr, nullptr, nullptr /* feature */, 0, nullptr, nullptr, nullptr}};
class FlagsStateTest : public ::testing::Test {
protected:
@@ -886,7 +893,7 @@
&supported_entries, &unsupported_entries,
base::Bind(&SkipFeatureEntry));
// All |kEntries| except for |kFlags3| should be supported.
- EXPECT_EQ(9u, supported_entries.GetSize());
+ EXPECT_EQ(10u, supported_entries.GetSize());
EXPECT_EQ(1u, unsupported_entries.GetSize());
EXPECT_EQ(arraysize(kEntries),
supported_entries.GetSize() + unsupported_entries.GetSize());
diff --git a/components/flags_ui/flags_ui_constants.cc b/components/flags_ui/flags_ui_constants.cc
index 2b902245b..a3e2b1c 100644
--- a/components/flags_ui/flags_ui_constants.cc
+++ b/components/flags_ui/flags_ui_constants.cc
@@ -12,6 +12,7 @@
// Message handlers.
const char kEnableExperimentalFeature[] = "enableExperimentalFeature";
const char kRequestExperimentalFeatures[] = "requestExperimentalFeatures";
+const char kSetOriginListFlag[] = "setOriginListFlag";
const char kResetAllFlags[] = "resetAllFlags";
const char kRestartBrowser[] = "restartBrowser";
diff --git a/components/flags_ui/flags_ui_constants.h b/components/flags_ui/flags_ui_constants.h
index 67442bc..5a3300a 100644
--- a/components/flags_ui/flags_ui_constants.h
+++ b/components/flags_ui/flags_ui_constants.h
@@ -15,6 +15,7 @@
// Must match the constants used in the resource files.
extern const char kEnableExperimentalFeature[];
extern const char kRequestExperimentalFeatures[];
+extern const char kSetOriginListFlag[];
extern const char kResetAllFlags[];
extern const char kRestartBrowser[];
diff --git a/components/flags_ui/resources/flags.css b/components/flags_ui/resources/flags.css
index faca70f..b068942 100644
--- a/components/flags_ui/resources/flags.css
+++ b/components/flags_ui/resources/flags.css
@@ -260,6 +260,17 @@
width: 100%;
}
+.experiment-origin-list-value {
+ border: 1px solid var(--color-light-gray);
+ border-radius: 3px;
+ box-sizing: border-box;
+ font-size: 1em;
+ margin: 0;
+ min-height: 3em;
+ padding: 8px;
+ width: 100%;
+}
+
.experiment-switched .experiment-actions select {
background: var(--google-blue-700);
color: white;
diff --git a/components/flags_ui/resources/flags.html b/components/flags_ui/resources/flags.html
index b19625a6..a91d6d6 100644
--- a/components/flags_ui/resources/flags.html
+++ b/components/flags_ui/resources/flags.html
@@ -87,6 +87,11 @@
<span jsvalues=".innerHTML:description"></span> –
<span class="platforms" jscontent="supported_platforms.join(', ')"></span>
</p>
+ <div jsdisplay="origin_list_value!==null">
+ <textarea class="experiment-origin-list-value"
+ jsvalues=".internal_name:internal_name; .value:origin_list_value"
+ tabindex="7"></textarea>
+ </div>
<a class="permalink" jsvalues="href: '#' + internal_name"
jscontent="'#' + internal_name" tabindex="6"></a>
</div>
@@ -126,6 +131,11 @@
<span jsvalues=".innerHTML:description"></span> –
<span class="platforms" jscontent="supported_platforms.join(', ')"></span>
</p>
+ <div jsdisplay="origin_list_value!==null">
+ <textarea class="experiment-origin-list-value"
+ jsvalues=".internal_name:internal_name; .value:origin_list_value"
+ tabindex="7"></textarea>
+ </div>
<a class="permalink" jsvalues="href: '#' + internal_name"
jscontent="'#' + internal_name" tabindex="6"></a>
</div>
@@ -177,7 +187,7 @@
</p>
<a class="permalink"
jsvalues="href: '#' + internal_name"
- jscontent="'#' + internal_name" tabindex="7"></a>
+ jscontent="'#' + internal_name" tabindex="8"></a>
</div>
<div class="flex experiment-actions">Not available on your platform.</div>
</div>
@@ -194,7 +204,7 @@
<div class="flex restart-notice" jstcache="0">$i18n{flagsRestartNotice}</div>
<div class="flex">
<if expr="not is_ios">
- <button class="experiment-restart-button" type="button" tabindex="8">
+ <button class="experiment-restart-button" type="button" tabindex="9">
<if expr="not is_chromeos">
Relaunch Now
</if>
diff --git a/components/flags_ui/resources/flags.js b/components/flags_ui/resources/flags.js
index 807533b7..4508cfa 100644
--- a/components/flags_ui/resources/flags.js
+++ b/components/flags_ui/resources/flags.js
@@ -36,6 +36,14 @@
};
}
+ elements = document.getElementsByClassName('experiment-origin-list-value');
+ for (var i = 0; i < elements.length; ++i) {
+ elements[i].onchange = function() {
+ handleSetOriginListFlag(this, this.value);
+ return false;
+ };
+ }
+
elements = document.getElementsByClassName('experiment-restart-button');
for (var i = 0; i < elements.length; ++i) {
elements[i].onclick = restartBrowser;
@@ -206,6 +214,11 @@
experimentChangesUiUpdates(node, enable ? 1 : 0);
}
+function handleSetOriginListFlag(node, value) {
+ chrome.send('setOriginListFlag', [String(node.internal_name), String(value)]);
+ $('needs-restart').classList.add('show');
+}
+
/**
* Invoked when the selection of a multi-value choice is changed to the
* specified index.
@@ -286,6 +299,9 @@
this.clearSearch.bind(this));
window.addEventListener('keyup', function(e) {
+ if (document.activeElement.nodeName == "TEXTAREA") {
+ return;
+ }
switch(e.key) {
case '/':
this.searchBox_.focus();