1 // Copyright 2018 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.
5 #include "base/command_line.h"
6 #include "base/strings/stringprintf.h"
7 #include "base/strings/utf_string_conversions.h"
8 #include "base/test/bind_test_util.h"
9 #include "base/test/scoped_feature_list.h"
10 #include "build/build_config.h"
11 #include "chrome/browser/about_flags.h"
12 #include "chrome/browser/ui/browser.h"
13 #include "chrome/browser/ui/tabs/tab_strip_model.h"
14 #include "chrome/browser/unexpire_flags.h"
15 #include "chrome/test/base/in_process_browser_test.h"
16 #include "chrome/test/base/interactive_test_utils.h"
17 #include "chrome/test/base/ui_test_utils.h"
18 #include "components/flags_ui/feature_entry_macros.h"
19 #include "content/public/browser/web_contents.h"
20 #include "content/public/test/browser_test.h"
21 #include "content/public/test/browser_test_utils.h"
22 #include "ui/base/window_open_disposition.h"
26 const char kSwitchName[] = "flag-system-test-switch";
27 const char kFlagName[] = "flag-system-test-flag-1";
29 const char kExpiredFlagName[] = "flag-system-test-flag-2";
30 const char kExpiredFlagSwitchName[] = "flag-system-test-expired-switch";
32 const char kFlagWithOptionSelectorName[] = "flag-system-test-flag-3";
33 const char kFlagWithOptionSelectorSwitchName[] = "flag-system-test-switch";
35 // Command line switch containing an invalid origin.
36 const char kUnsanitizedCommandLine[] =
37 "http://example-cmdline.test,invalid-cmdline";
39 // Command line switch without the invalid origin.
40 const char kSanitizedCommandLine[] = "http://example-cmdline.test";
42 // User input containing invalid origins.
43 const char kUnsanitizedInput[] =
44 "http://example.test/path http://example2.test/?query\n"
45 "invalid-value, filesystem:http://example.test.file, "
46 "ws://example3.test http://&^.com";
48 // User input with invalid origins removed and formatted.
49 const char kSanitizedInput[] =
50 "http://example.test,http://example2.test,ws://example3.test";
52 // Command Line + user input with invalid origins removed and formatted.
53 const char kSanitizedInputAndCommandLine[] =
54 "http://example-cmdline.test,http://example.test,http://"
55 "example2.test,ws://example3.test";
57 void SimulateTextType(content::WebContents* contents,
58 const char* experiment_id,
60 EXPECT_TRUE(content::ExecuteScript(
61 contents, base::StringPrintf(
62 "var parent = document.getElementById('%s');"
63 "var textarea = parent.getElementsByTagName('textarea')[0];"
65 "textarea.value = `%s`;"
66 "textarea.onchange();",
67 experiment_id, text)));
70 void ToggleEnableDropdown(content::WebContents* contents,
71 const char* experiment_id,
73 EXPECT_TRUE(content::ExecuteScript(
76 "var k = document.getElementById('%s');"
77 "var s = k.getElementsByClassName('experiment-enable-disable')[0];"
79 "s.selectedIndex = %d;"
81 experiment_id, enable ? 1 : 0)));
84 std::string GetOriginListText(content::WebContents* contents,
85 const char* experiment_id) {
87 EXPECT_TRUE(content::ExecuteScriptAndExtractString(
90 "var k = document.getElementById('%s');"
91 "var s = k.getElementsByClassName('experiment-origin-list-value')[0];"
92 "window.domAutomationController.send(s.value );",
98 bool IsDropdownEnabled(content::WebContents* contents,
99 const char* experiment_id) {
101 EXPECT_TRUE(content::ExecuteScriptAndExtractBool(
104 "var k = document.getElementById('%s');"
105 "var s = k.getElementsByClassName('experiment-enable-disable')[0];"
106 "window.domAutomationController.send(s.value == 'enabled');",
112 bool IsFlagPresent(content::WebContents* contents, const char* experiment_id) {
114 EXPECT_TRUE(content::ExecuteScriptAndExtractBool(
116 base::StringPrintf("var k = document.getElementById('%s');"
117 "window.domAutomationController.send(k != null);",
123 void WaitForExperimentalFeatures(content::WebContents* contents) {
125 ASSERT_TRUE(content::ExecuteScriptAndExtractBool(
127 "experimentalFeaturesReady.then(() => {"
128 " window.domAutomationController.send(true);"
133 // In these tests, valid origins in the existing command line flag will be
134 // appended to the list entered by the user in chrome://flags.
135 // The tests are run twice for each bool value: Once with an existing command
136 // line (provided in SetUpCommandLine) and once without.
137 class AboutFlagsBrowserTest : public InProcessBrowserTest,
138 public testing::WithParamInterface<bool> {
140 AboutFlagsBrowserTest() {
141 about_flags::testing::SetFeatureEntries(
142 {{kFlagName, "name-1", "description-1", -1,
143 ORIGIN_LIST_VALUE_TYPE(kSwitchName, "")},
144 {kExpiredFlagName, "name-2", "description-2", -1,
145 SINGLE_VALUE_TYPE(kExpiredFlagSwitchName)},
146 {kFlagWithOptionSelectorName, "name-3", "description-3", -1,
147 SINGLE_VALUE_TYPE(kFlagWithOptionSelectorSwitchName)}});
148 flags::testing::SetFlagExpiredPredicate(
149 base::BindLambdaForTesting([&](const std::string& name) -> bool {
150 return expiration_enabled_ && name == kExpiredFlagName;
154 void SetUpCommandLine(base::CommandLine* command_line) override {
155 command_line->AppendSwitchASCII(kSwitchName, GetInitialCommandLine());
159 void set_expiration_enabled(bool enabled) { expiration_enabled_ = enabled; }
161 bool has_initial_command_line() const { return GetParam(); }
163 std::string GetInitialCommandLine() const {
164 return has_initial_command_line() ? kUnsanitizedCommandLine : std::string();
167 std::string GetSanitizedCommandLine() const {
168 return has_initial_command_line() ? kSanitizedCommandLine : std::string();
171 std::string GetSanitizedInputAndCommandLine() const {
172 return has_initial_command_line() ? kSanitizedInputAndCommandLine
176 void NavigateToFlagsPage() {
177 ui_test_utils::NavigateToURL(browser(), GURL("chrome://flags"));
178 WaitForExperimentalFeatures(
179 browser()->tab_strip_model()->GetActiveWebContents());
182 bool expiration_enabled_ = true;
184 base::test::ScopedFeatureList feature_list_;
187 INSTANTIATE_TEST_SUITE_P(All,
188 AboutFlagsBrowserTest,
189 ::testing::Values(true, false));
191 // Goes to chrome://flags page, types text into an ORIGIN_LIST_VALUE field but
192 // does not enable the feature.
193 IN_PROC_BROWSER_TEST_P(AboutFlagsBrowserTest, PRE_OriginFlagDisabled) {
194 NavigateToFlagsPage();
196 const base::CommandLine::SwitchMap kInitialSwitches =
197 base::CommandLine::ForCurrentProcess()->GetSwitches();
199 content::WebContents* contents =
200 browser()->tab_strip_model()->GetActiveWebContents();
202 // The page should be populated with the sanitized command line value.
203 EXPECT_EQ(GetSanitizedCommandLine(), GetOriginListText(contents, kFlagName));
205 // Type a value in the experiment's textarea. Since the flag state is
206 // "Disabled" by default, command line shouldn't change.
207 SimulateTextType(contents, kFlagName, kUnsanitizedInput);
208 EXPECT_EQ(kInitialSwitches,
209 base::CommandLine::ForCurrentProcess()->GetSwitches());
211 // Input should be restored after a page reload.
212 NavigateToFlagsPage();
213 EXPECT_EQ(GetSanitizedInputAndCommandLine(),
214 GetOriginListText(contents, kFlagName));
217 // Flaky. http://crbug.com/1010678
218 IN_PROC_BROWSER_TEST_P(AboutFlagsBrowserTest, DISABLED_OriginFlagDisabled) {
219 // Even though the feature is disabled, the switch is set directly via command
222 GetInitialCommandLine(),
223 base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(kSwitchName));
225 NavigateToFlagsPage();
226 content::WebContents* contents =
227 browser()->tab_strip_model()->GetActiveWebContents();
228 EXPECT_FALSE(IsDropdownEnabled(contents, kFlagName));
229 EXPECT_EQ(GetSanitizedInputAndCommandLine(),
230 GetOriginListText(contents, kFlagName));
233 // Goes to chrome://flags page, types text into an ORIGIN_LIST_VALUE field and
234 // enables the feature.
235 IN_PROC_BROWSER_TEST_P(AboutFlagsBrowserTest, PRE_OriginFlagEnabled) {
236 NavigateToFlagsPage();
238 const base::CommandLine::SwitchMap kInitialSwitches =
239 base::CommandLine::ForCurrentProcess()->GetSwitches();
241 content::WebContents* contents =
242 browser()->tab_strip_model()->GetActiveWebContents();
244 // The page should be populated with the sanitized command line value.
245 EXPECT_EQ(GetSanitizedCommandLine(), GetOriginListText(contents, kFlagName));
247 // Type a value in the experiment's textarea. Since the flag state is
248 // "Disabled" by default, command line shouldn't change.
249 SimulateTextType(contents, kFlagName, kUnsanitizedInput);
250 EXPECT_EQ(kInitialSwitches,
251 base::CommandLine::ForCurrentProcess()->GetSwitches());
253 // Enable the experiment. The behavior is different between ChromeOS and
255 ToggleEnableDropdown(contents, kFlagName, true);
257 #if !defined(OS_CHROMEOS)
258 // On non-ChromeOS, the command line is not modified until restart.
259 EXPECT_EQ(kInitialSwitches,
260 base::CommandLine::ForCurrentProcess()->GetSwitches());
262 // On ChromeOS, the command line is immediately modified.
263 EXPECT_NE(kInitialSwitches,
264 base::CommandLine::ForCurrentProcess()->GetSwitches());
266 GetSanitizedInputAndCommandLine(),
267 base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(kSwitchName));
270 // Input should be restored after a page reload.
271 NavigateToFlagsPage();
272 EXPECT_EQ(GetSanitizedInputAndCommandLine(),
273 GetOriginListText(contents, kFlagName));
276 // Flaky. http://crbug.com/1010678
277 IN_PROC_BROWSER_TEST_P(AboutFlagsBrowserTest, DISABLED_OriginFlagEnabled) {
278 #if !defined(OS_CHROMEOS)
279 // On non-ChromeOS, the command line is modified after restart.
281 GetSanitizedInputAndCommandLine(),
282 base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(kSwitchName));
284 // On ChromeOS, the command line isn't modified after restart.
286 GetInitialCommandLine(),
287 base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(kSwitchName));
290 NavigateToFlagsPage();
291 content::WebContents* contents =
292 browser()->tab_strip_model()->GetActiveWebContents();
293 EXPECT_TRUE(IsDropdownEnabled(contents, kFlagName));
294 EXPECT_EQ(GetSanitizedInputAndCommandLine(),
295 GetOriginListText(contents, kFlagName));
297 #if defined(OS_CHROMEOS)
298 // ChromeOS doesn't read chrome://flags values on startup so we explicitly
299 // need to disable and re-enable the flag here.
300 ToggleEnableDropdown(contents, kFlagName, true);
304 GetSanitizedInputAndCommandLine(),
305 base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(kSwitchName));
308 // Crashes on Win. http://crbug.com/1025213
310 #define MAYBE_ExpiryHidesFlag DISABLED_ExpiryHidesFlag
312 #define MAYBE_ExpiryHidesFlag ExpiryHidesFlag
314 IN_PROC_BROWSER_TEST_P(AboutFlagsBrowserTest, MAYBE_ExpiryHidesFlag) {
315 NavigateToFlagsPage();
316 content::WebContents* contents =
317 browser()->tab_strip_model()->GetActiveWebContents();
318 EXPECT_TRUE(IsFlagPresent(contents, kFlagName));
319 EXPECT_FALSE(IsFlagPresent(contents, kExpiredFlagName));
321 set_expiration_enabled(false);
323 NavigateToFlagsPage();
324 contents = browser()->tab_strip_model()->GetActiveWebContents();
325 EXPECT_TRUE(IsFlagPresent(contents, kFlagName));
326 EXPECT_TRUE(IsFlagPresent(contents, kExpiredFlagName));
329 #if !defined(OS_CHROMEOS)
330 IN_PROC_BROWSER_TEST_P(AboutFlagsBrowserTest, PRE_ExpiredFlagDoesntApply) {
331 set_expiration_enabled(false);
332 NavigateToFlagsPage();
333 content::WebContents* contents =
334 browser()->tab_strip_model()->GetActiveWebContents();
335 EXPECT_TRUE(IsFlagPresent(contents, kExpiredFlagName));
336 EXPECT_FALSE(IsDropdownEnabled(contents, kExpiredFlagName));
338 ToggleEnableDropdown(contents, kExpiredFlagName, true);
341 // Flaky everywhere: https://crbug.com/1024028
342 IN_PROC_BROWSER_TEST_P(AboutFlagsBrowserTest, DISABLED_ExpiredFlagDoesntApply) {
343 set_expiration_enabled(true);
344 NavigateToFlagsPage();
345 content::WebContents* contents =
346 browser()->tab_strip_model()->GetActiveWebContents();
347 EXPECT_FALSE(IsFlagPresent(contents, kExpiredFlagName));
349 EXPECT_FALSE(base::CommandLine::ForCurrentProcess()->HasSwitch(
350 kExpiredFlagSwitchName));
354 IN_PROC_BROWSER_TEST_P(AboutFlagsBrowserTest, FormRestore) {
355 NavigateToFlagsPage();
356 content::WebContents* contents =
357 browser()->tab_strip_model()->GetActiveWebContents();
359 // Remove the internal_name property from a flag's selector, then synthesize a
360 // change event for it. This simulates what happens during form restoration in
361 // Blink, when navigating back and then forward to the flags page. This test
362 // ensures that that does not crash the browser.
363 // See https://crbug.com/1038638 for more details.
364 EXPECT_TRUE(content::ExecJs(
367 "var k = document.getElementById('%s');"
368 "var s = k.getElementsByClassName('experiment-enable-disable')[0];"
369 "delete s.internal_name;"
370 "const e = document.createEvent('HTMLEvents');"
371 "e.initEvent('change', true, true);"
372 "s.dispatchEvent(e);",
373 kFlagWithOptionSelectorName)));