1 // Copyright 2014 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/message_loop/message_loop.h"
6 #include "base/prefs/pref_service.h"
7 #include "base/strings/stringprintf.h"
8 #include "base/task_runner.h"
9 #include "chrome/browser/browser_process.h"
10 #include "chrome/browser/chrome_notification_types.h"
11 #include "chrome/browser/chromeos/customization/customization_document.h"
12 #include "chrome/browser/chromeos/input_method/input_method_util.h"
13 #include "chrome/browser/chromeos/login/login_manager_test.h"
14 #include "chrome/browser/chromeos/login/login_wizard.h"
15 #include "chrome/browser/chromeos/login/screens/network_screen.h"
16 #include "chrome/browser/chromeos/login/test/js_checker.h"
17 #include "chrome/browser/chromeos/login/ui/login_display_host_impl.h"
18 #include "chrome/browser/chromeos/login/wizard_controller.h"
19 #include "chrome/common/pref_names.h"
20 #include "chrome/test/base/in_process_browser_test.h"
21 #include "chromeos/system/fake_statistics_provider.h"
22 #include "chromeos/system/statistics_provider.h"
23 #include "content/public/browser/notification_service.h"
24 #include "content/public/browser/web_contents.h"
25 #include "content/public/test/browser_test_utils.h"
26 #include "content/public/test/test_utils.h"
27 #include "ui/base/ime/chromeos/extension_ime_util.h"
28 #include "ui/base/ime/chromeos/input_method_manager.h"
29 #include "ui/base/ime/chromeos/input_method_whitelist.h"
40 const char kLocaleSelect
[] = "language-select";
41 const char kKeyboardSelect
[] = "keyboard-select";
43 const char kUSLayout
[] = "xkb:us::eng";
45 class LanguageListWaiter
: public NetworkScreen::Observer
{
47 explicit LanguageListWaiter(base::RunLoop
& loop
)
49 NetworkScreen::Get(WizardController::default_controller())),
51 network_screen_
->AddObserver(this);
55 ~LanguageListWaiter() override
{ network_screen_
->RemoveObserver(this); }
57 // NetworkScreen::Observer implementation:
58 void OnLanguageListReloaded() override
{ CheckLanguageList(); }
61 void CheckLanguageList() {
62 if (network_screen_
->GetLanguageList())
66 NetworkScreen
* network_screen_
;
72 struct LocalizationTestParams
{
73 const char* initial_locale
;
74 const char* keyboard_layout
;
75 const char* expected_locale
;
76 const char* expected_keyboard_layout
;
77 const char* expected_keyboard_select_control
;
78 } const oobe_localization_test_parameters
[] = {
79 // ------------------ Non-Latin setup
80 // For a non-Latin keyboard layout like Russian, we expect to see the US
82 {"ru", "xkb:ru::rus", "ru", kUSLayout
, "xkb:us::eng"},
83 {"ru", "xkb:us::eng,xkb:ru::rus", "ru", kUSLayout
, "xkb:us::eng"},
85 // IMEs do not load at OOBE, so we just expect to see the (Latin) Japanese
87 {"ja", "xkb:jp::jpn", "ja", "xkb:jp::jpn", "xkb:jp::jpn,[xkb:us::eng]"},
89 // We don't use the Icelandic locale but the Icelandic keyboard layout
90 // should still be selected when specified as the default.
95 "xkb:is::ice,[xkb:us::eng,xkb:us:intl:eng,xkb:us:altgr-intl:eng,"
96 "xkb:us:dvorak:eng,xkb:us:dvp:eng,xkb:us:colemak:eng]"},
97 // ------------------ Full Latin setup
98 // French Swiss keyboard.
103 "xkb:ch:fr:fra,[xkb:fr::fra,xkb:be::fra,xkb:ca::fra,"
104 "xkb:ca:multix:fra,xkb:us::eng]"},
106 // German Swiss keyboard.
111 "xkb:ch::ger,[xkb:de::ger,xkb:de:neo:ger,xkb:be::ger,xkb:us::eng]"},
113 // NetworkScreenMultipleLocales
118 "xkb:be::nld,[xkb:es::spa,xkb:latam::spa,xkb:us::eng]"},
120 {"ru,de", "xkb:ru::rus", "ru,de", kUSLayout
, "xkb:us::eng"},
122 // TODO(alemate/michaelpg): Figure out why these tests are failing
123 // and re-enable them. crbug.com/422702.
124 // ------------------ Regional Locales
125 // Syntetic example to test correct merging of different locales.
126 // {"fr-CH,it-CH,de-CH",
127 // "xkb:fr::fra,xkb:it::ita,xkb:de::ger",
128 // "fr-CH,it-CH,de-CH",
130 // "xkb:fr::fra,xkb:it::ita,xkb:de::ger,[xkb:be::fra,xkb:ca::fra,"
131 // "xkb:ch:fr:fra,xkb:ca:multix:fra,xkb:us::eng]"},
133 // Another syntetic example. Check that british keyboard is available.
138 // "xkb:us::eng,[xkb:gb:extd:eng,xkb:gb:dvorak:eng]"},
141 class OobeLocalizationTest
142 : public LoginManagerTest
,
143 public testing::WithParamInterface
<const LocalizationTestParams
*> {
145 OobeLocalizationTest();
147 // Verifies that the comma-separated |values| corresponds with the first
148 // values in |select_id|, optionally checking for an options group label after
149 // the first set of options.
150 bool VerifyInitialOptions(const char* select_id
,
152 bool check_separator
);
154 // Verifies that |value| exists in |select_id|.
155 bool VerifyOptionExists(const char* select_id
, const char* value
);
157 // Dumps OOBE select control (language or keyboard) to string.
158 std::string
DumpOptions(const char* select_id
);
161 // Runs the test for the given locale and keyboard layout.
162 void RunLocalizationTest();
164 void WaitUntilJSIsReady() {
165 LoginDisplayHostImpl
* host
= static_cast<LoginDisplayHostImpl
*>(
166 LoginDisplayHostImpl::default_host());
169 chromeos::OobeUI
* oobe_ui
= host
->GetOobeUI();
172 base::RunLoop run_loop
;
173 const bool oobe_ui_ready
= oobe_ui
->IsJSReady(run_loop
.QuitClosure());
179 system::ScopedFakeStatisticsProvider fake_statistics_provider_
;
180 test::JSChecker checker
;
182 DISALLOW_COPY_AND_ASSIGN(OobeLocalizationTest
);
185 OobeLocalizationTest::OobeLocalizationTest() : LoginManagerTest(false) {
186 fake_statistics_provider_
.SetMachineStatistic("initial_locale",
187 GetParam()->initial_locale
);
188 fake_statistics_provider_
.SetMachineStatistic("keyboard_layout",
189 GetParam()->keyboard_layout
);
192 bool OobeLocalizationTest::VerifyInitialOptions(const char* select_id
,
194 bool check_separator
) {
195 const std::string expression
= base::StringPrintf(
197 " var select = document.querySelector('#%s');\n"
200 " var values = '%s'.split(',');\n"
201 " var correct = select.selectedIndex == 0;\n"
202 " for (var i = 0; i < values.length && correct; i++) {\n"
203 " if (select.options[i].value != values[i])\n"
204 " correct = false;\n"
206 " if (%d && correct)\n"
207 " correct = select.children[values.length].tagName === 'OPTGROUP';\n"
209 "})()", select_id
, values
, check_separator
);
210 const bool execute_status
= checker
.GetBool(expression
);
211 EXPECT_TRUE(execute_status
) << expression
;
212 return execute_status
;
215 bool OobeLocalizationTest::VerifyOptionExists(const char* select_id
,
217 const std::string expression
= base::StringPrintf(
219 " var select = document.querySelector('#%s');\n"
222 " for (var i = 0; i < select.options.length; i++) {\n"
223 " if (select.options[i].value == '%s')\n"
227 "})()", select_id
, value
);
228 const bool execute_status
= checker
.GetBool(expression
);
229 EXPECT_TRUE(execute_status
) << expression
;
230 return execute_status
;
233 std::string
OobeLocalizationTest::DumpOptions(const char* select_id
) {
234 const std::string expression
= base::StringPrintf(
237 " var selector = '#%s';\n"
238 " var divider = ',';\n"
239 " var select = document.querySelector(selector);\n"
241 " return 'document.querySelector(' + selector + ') failed.';\n"
242 " var dumpOptgroup = function(group) {\n"
243 " var result = '';\n"
244 " for (var i = 0; i < group.children.length; i++) {\n"
246 " result += divider;\n"
248 " if (group.children[i].value) {\n"
249 " result += group.children[i].value;\n"
251 " result += '__NO_VALUE__';\n"
256 " var result = '';\n"
257 " if (select.selectedIndex != 0) {\n"
258 " result += '(selectedIndex=' + select.selectedIndex + \n"
259 " ', selected \"' + select.options[select.selectedIndex].value +\n"
262 " var children = select.children;\n"
263 " for (var i = 0; i < children.length; i++) {\n"
265 " result += divider;\n"
267 " if (children[i].value) {\n"
268 " result += children[i].value;\n"
269 " } else if (children[i].tagName === 'OPTGROUP') {\n"
270 " result += '[' + dumpOptgroup(children[i]) + ']';\n"
272 " result += '__NO_VALUE__';\n"
278 return checker
.GetString(expression
);
281 std::string
TranslateXKB2Extension(const std::string
& src
) {
282 std::string
result(src
);
283 // Modifies the expected keyboard select control options for the new
284 // extension based xkb id.
286 std::string repl_old
= "xkb:";
287 std::string repl_new
=
288 extension_ime_util::GetInputMethodIDByEngineID("xkb:");
289 while ((pos
= result
.find(repl_old
, pos
)) != std::string::npos
) {
290 result
.replace(pos
, repl_old
.length(), repl_new
);
291 pos
+= repl_new
.length();
296 void OobeLocalizationTest::RunLocalizationTest() {
297 const std::string
initial_locale(GetParam()->initial_locale
);
298 const std::string
keyboard_layout(GetParam()->keyboard_layout
);
299 const std::string
expected_locale(GetParam()->expected_locale
);
300 const std::string
expected_keyboard_layout(
301 GetParam()->expected_keyboard_layout
);
302 const std::string
expected_keyboard_select_control(
303 GetParam()->expected_keyboard_select_control
);
305 const std::string expected_keyboard_select
=
306 TranslateXKB2Extension(expected_keyboard_select_control
);
310 LanguageListWaiter
waiter(loop
);
314 WaitUntilJSIsReady();
316 const std::string first_language
=
317 expected_locale
.substr(0, expected_locale
.find(','));
319 const std::string waiting_script
= base::StringPrintf(
320 "var screenElement = document.getElementById('language-select');"
321 "function SendReplyIfAcceptEnabled() {"
322 " if ($('language-select').value != '%s')"
324 " domAutomationController.send(true);"
325 " observer.disconnect();"
328 "var observer = new MutationObserver(SendReplyIfAcceptEnabled);"
329 "if (!SendReplyIfAcceptEnabled()) {"
330 " var options = { attributes: true };"
331 " observer.observe(screenElement, options);"
333 first_language
.c_str());
335 ASSERT_TRUE(content::ExecuteScriptAndExtractBool(
336 static_cast<chromeos::LoginDisplayHostImpl
*>(
337 chromeos::LoginDisplayHostImpl::default_host())
344 checker
.set_web_contents(static_cast<chromeos::LoginDisplayHostImpl
*>(
345 chromeos::LoginDisplayHostImpl::default_host())->
346 GetOobeUI()->web_ui()->GetWebContents());
348 if (!VerifyInitialOptions(kLocaleSelect
, expected_locale
.c_str(), true)) {
349 LOG(ERROR
) << "Actual value of " << kLocaleSelect
<< ":\n"
350 << DumpOptions(kLocaleSelect
);
352 if (!VerifyInitialOptions(
354 TranslateXKB2Extension(expected_keyboard_layout
).c_str(),
356 LOG(ERROR
) << "Actual value of " << kKeyboardSelect
<< ":\n"
357 << DumpOptions(kKeyboardSelect
);
360 // Make sure we have a fallback keyboard.
361 if (!VerifyOptionExists(kKeyboardSelect
,
362 extension_ime_util::GetInputMethodIDByEngineID(
363 kUSLayout
).c_str())) {
364 LOG(ERROR
) << "Actual value of " << kKeyboardSelect
<< ":\n"
365 << DumpOptions(kKeyboardSelect
);
368 // Note, that sort order is locale-specific, but is unlikely to change.
369 // Especially for keyboard layouts.
370 EXPECT_EQ(expected_keyboard_select
, DumpOptions(kKeyboardSelect
));
372 // Shut down the display host.
373 chromeos::LoginDisplayHostImpl::default_host()->Finalize();
374 base::MessageLoopForUI::current()->RunUntilIdle();
376 // Clear the locale pref so the statistics provider is pinged next time.
377 g_browser_process
->local_state()->SetString(prefs::kApplicationLocale
,
381 IN_PROC_BROWSER_TEST_P(OobeLocalizationTest
, LocalizationTest
) {
382 RunLocalizationTest();
385 INSTANTIATE_TEST_CASE_P(
387 OobeLocalizationTest
,
388 testing::Range(&oobe_localization_test_parameters
[0],
389 &oobe_localization_test_parameters
[arraysize(
390 oobe_localization_test_parameters
)]));
391 } // namespace chromeos