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.
4
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_document.h"
12 #include "chrome/browser/chromeos/input_method/input_method_util.h"
13 #include "chrome/browser/chromeos/login/login_wizard.h"
14 #include "chrome/browser/chromeos/login/test/js_checker.h"
15 #include "chrome/browser/chromeos/login/ui/login_display_host_impl.h"
16 #include "chrome/common/pref_names.h"
17 #include "chrome/test/base/in_process_browser_test.h"
18 #include "chromeos/ime/extension_ime_util.h"
19 #include "chromeos/ime/input_method_manager.h"
20 #include "chromeos/ime/input_method_whitelist.h"
21 #include "chromeos/system/statistics_provider.h"
22 #include "content/public/browser/notification_service.h"
23 #include "content/public/browser/web_contents.h"
24 #include "content/public/test/browser_test_utils.h"
25 #include "content/public/test/test_utils.h"
26
27 namespace base {
28 class TaskRunner;
29 }
30
31 namespace chromeos {
32
33 namespace {
34
35 // OOBE constants.
36 const char* kLocaleSelect = "language-select";
37 const char* kKeyboardSelect = "keyboard-select";
38
39 const char* kUSLayout = "xkb:us::eng";
40
41 }
42
43 namespace system {
44
45 // Custom StatisticsProvider that will return each set of region settings.
46 class FakeStatisticsProvider : public StatisticsProvider {
47 public:
~FakeStatisticsProvider()48 virtual ~FakeStatisticsProvider() {}
49
set_locale(const std::string & locale)50 void set_locale(const std::string& locale) {
51 initial_locale_ = locale;
52 }
53
set_keyboard_layout(const std::string & keyboard_layout)54 void set_keyboard_layout(const std::string& keyboard_layout) {
55 keyboard_layout_ = keyboard_layout;
56 }
57
58 private:
59 // StatisticsProvider overrides.
StartLoadingMachineStatistics(const scoped_refptr<base::TaskRunner> & file_task_runner,bool load_oem_manifest)60 virtual void StartLoadingMachineStatistics(
61 const scoped_refptr<base::TaskRunner>& file_task_runner,
62 bool load_oem_manifest) OVERRIDE {
63 }
64
65 // Populates the named machine statistic for initial_locale and
66 // keyboard_layout only.
GetMachineStatistic(const std::string & name,std::string * result)67 virtual bool GetMachineStatistic(const std::string& name,
68 std::string* result) OVERRIDE {
69 if (name == "initial_locale")
70 *result = initial_locale_;
71 else if (name == "keyboard_layout")
72 *result = keyboard_layout_;
73 else
74 return false;
75
76 return true;
77 }
78
GetMachineFlag(const std::string & name,bool * result)79 virtual bool GetMachineFlag(const std::string& name, bool* result) OVERRIDE {
80 return false;
81 }
82
Shutdown()83 virtual void Shutdown() OVERRIDE {
84 }
85
86 std::string initial_locale_;
87 std::string keyboard_layout_;
88 };
89
90 } // namespace system
91
92 class OobeLocalizationTest : public InProcessBrowserTest {
93 public:
94 OobeLocalizationTest();
95 virtual ~OobeLocalizationTest();
96
97 // Verifies that the comma-separated |values| corresponds with the first
98 // values in |select_id|, optionally checking for an options group label after
99 // the first set of options.
100 bool VerifyInitialOptions(const char* select_id,
101 const char* values,
102 bool check_separator);
103
104 // Verifies that |value| exists in |select_id|.
105 bool VerifyOptionExists(const char* select_id, const char* value);
106
107 // Dumps OOBE select control (language or keyboard) to string.
108 std::string DumpOptions(const char* select_id);
109
110 protected:
111 // Runs the test for the given locale and keyboard layout.
112 void RunLocalizationTest(const std::string& initial_locale,
113 const std::string& keyboard_layout,
114 const std::string& expected_locale,
115 const std::string& expected_keyboard_layout,
116 const std::string& expected_keyboard_select_control);
117
118 private:
119 scoped_ptr<system::FakeStatisticsProvider> statistics_provider_;
120 test::JSChecker checker;
121
122 DISALLOW_COPY_AND_ASSIGN(OobeLocalizationTest);
123 };
124
OobeLocalizationTest()125 OobeLocalizationTest::OobeLocalizationTest() {
126 statistics_provider_.reset(new system::FakeStatisticsProvider());
127 // Set the instance returned by GetInstance() for testing.
128 system::StatisticsProvider::SetTestProvider(statistics_provider_.get());
129 }
130
~OobeLocalizationTest()131 OobeLocalizationTest::~OobeLocalizationTest() {
132 system::StatisticsProvider::SetTestProvider(NULL);
133 }
134
VerifyInitialOptions(const char * select_id,const char * values,bool check_separator)135 bool OobeLocalizationTest::VerifyInitialOptions(const char* select_id,
136 const char* values,
137 bool check_separator) {
138 const std::string expression = base::StringPrintf(
139 "(function () {\n"
140 " var select = document.querySelector('#%s');\n"
141 " if (!select)\n"
142 " return false;\n"
143 " var values = '%s'.split(',');\n"
144 " var correct = select.selectedIndex == 0;\n"
145 " for (var i = 0; i < values.length && correct; i++) {\n"
146 " if (select.options[i].value != values[i])\n"
147 " correct = false;\n"
148 " }\n"
149 " if (%d && correct)\n"
150 " correct = select.children[values.length].tagName === 'OPTGROUP';\n"
151 " return correct;\n"
152 "})()", select_id, values, check_separator);
153 const bool execute_status = checker.GetBool(expression);
154 EXPECT_TRUE(execute_status) << expression;
155 return execute_status;
156 }
157
VerifyOptionExists(const char * select_id,const char * value)158 bool OobeLocalizationTest::VerifyOptionExists(const char* select_id,
159 const char* value) {
160 const std::string expression = base::StringPrintf(
161 "(function () {\n"
162 " var select = document.querySelector('#%s');\n"
163 " if (!select)\n"
164 " return false;\n"
165 " for (var i = 0; i < select.options.length; i++) {\n"
166 " if (select.options[i].value == '%s')\n"
167 " return true;\n"
168 " }\n"
169 " return false;\n"
170 "})()", select_id, value);
171 const bool execute_status = checker.GetBool(expression);
172 EXPECT_TRUE(execute_status) << expression;
173 return execute_status;
174 }
175
DumpOptions(const char * select_id)176 std::string OobeLocalizationTest::DumpOptions(const char* select_id) {
177 const std::string expression = base::StringPrintf(
178 "\n"
179 "(function () {\n"
180 " var selector = '#%s';\n"
181 " var divider = ',';\n"
182 " var select = document.querySelector(selector);\n"
183 " if (!select)\n"
184 " return 'document.querySelector(' + selector + ') failed.';\n"
185 " var dumpOptgroup = function(group) {\n"
186 " var result = '';\n"
187 " for (var i = 0; i < group.children.length; i++) {\n"
188 " if (i > 0) {\n"
189 " result += divider;\n"
190 " }\n"
191 " if (group.children[i].value) {\n"
192 " result += group.children[i].value;\n"
193 " } else {\n"
194 " result += '__NO_VALUE__';\n"
195 " }\n"
196 " }\n"
197 " return result;\n"
198 " };\n"
199 " var result = '';\n"
200 " if (select.selectedIndex != 0) {\n"
201 " result += '(selectedIndex=' + select.selectedIndex + \n"
202 " ', selected \"' + select.options[select.selectedIndex].value +\n"
203 " '\")';\n"
204 " }\n"
205 " var children = select.children;\n"
206 " for (var i = 0; i < children.length; i++) {\n"
207 " if (i > 0) {\n"
208 " result += divider;\n"
209 " }\n"
210 " if (children[i].value) {\n"
211 " result += children[i].value;\n"
212 " } else if (children[i].tagName === 'OPTGROUP') {\n"
213 " result += '[' + dumpOptgroup(children[i]) + ']';\n"
214 " } else {\n"
215 " result += '__NO_VALUE__';\n"
216 " }\n"
217 " }\n"
218 " return result;\n"
219 "})()\n",
220 select_id);
221 return checker.GetString(expression);
222 }
223
TranslateXKB2Extension(const std::string & src)224 std::string TranslateXKB2Extension(const std::string& src) {
225 std::string result(src);
226 // Modifies the expected keyboard select control options for the new
227 // extension based xkb id.
228 size_t pos = 0;
229 std::string repl_old = "xkb:";
230 std::string repl_new =
231 extension_ime_util::GetInputMethodIDByEngineID("xkb:");
232 while ((pos = result.find(repl_old, pos)) != std::string::npos) {
233 result.replace(pos, repl_old.length(), repl_new);
234 pos += repl_new.length();
235 }
236 return result;
237 }
238
RunLocalizationTest(const std::string & initial_locale,const std::string & keyboard_layout,const std::string & expected_locale,const std::string & expected_keyboard_layout,const std::string & expected_keyboard_select_control)239 void OobeLocalizationTest::RunLocalizationTest(
240 const std::string& initial_locale,
241 const std::string& keyboard_layout,
242 const std::string& expected_locale,
243 const std::string& expected_keyboard_layout,
244 const std::string& expected_keyboard_select_control) {
245 statistics_provider_->set_locale(initial_locale);
246 statistics_provider_->set_keyboard_layout(keyboard_layout);
247
248 // Initialize StartupCustomizationDocument with fake statistics provider.
249 StartupCustomizationDocument::GetInstance()->Init(
250 statistics_provider_.get());
251
252 g_browser_process->local_state()->SetString(
253 prefs::kHardwareKeyboardLayout, keyboard_layout);
254
255 input_method::InputMethodManager::Get()
256 ->GetInputMethodUtil()
257 ->InitXkbInputMethodsForTesting();
258
259 const std::string expected_keyboard_select =
260 TranslateXKB2Extension(expected_keyboard_select_control);
261
262 // Bring up the OOBE network screen.
263 chromeos::ShowLoginWizard(chromeos::WizardController::kNetworkScreenName);
264 content::WindowedNotificationObserver(
265 chrome::NOTIFICATION_LOGIN_OR_LOCK_WEBUI_VISIBLE,
266 content::NotificationService::AllSources()).Wait();
267
268 checker.set_web_contents(static_cast<chromeos::LoginDisplayHostImpl*>(
269 chromeos::LoginDisplayHostImpl::default_host())->
270 GetOobeUI()->web_ui()->GetWebContents());
271
272 if (!VerifyInitialOptions(kLocaleSelect, expected_locale.c_str(), true)) {
273 LOG(ERROR) << "Actual value of " << kLocaleSelect << ":\n"
274 << DumpOptions(kLocaleSelect);
275 }
276 if (!VerifyInitialOptions(
277 kKeyboardSelect,
278 TranslateXKB2Extension(expected_keyboard_layout).c_str(),
279 false)) {
280 LOG(ERROR) << "Actual value of " << kKeyboardSelect << ":\n"
281 << DumpOptions(kKeyboardSelect);
282 }
283
284 // Make sure we have a fallback keyboard.
285 if (!VerifyOptionExists(kKeyboardSelect,
286 extension_ime_util::GetInputMethodIDByEngineID(
287 kUSLayout).c_str())) {
288 LOG(ERROR) << "Actual value of " << kKeyboardSelect << ":\n"
289 << DumpOptions(kKeyboardSelect);
290 }
291
292 // Note, that sort order is locale-specific, but is unlikely to change.
293 // Especially for keyboard layouts.
294 EXPECT_EQ(expected_keyboard_select, DumpOptions(kKeyboardSelect));
295
296 // Shut down the display host.
297 chromeos::LoginDisplayHostImpl::default_host()->Finalize();
298 base::MessageLoopForUI::current()->RunUntilIdle();
299
300 // Clear the locale pref so the statistics provider is pinged next time.
301 g_browser_process->local_state()->SetString(prefs::kApplicationLocale,
302 std::string());
303 }
304
IN_PROC_BROWSER_TEST_F(OobeLocalizationTest,NetworkScreenNonLatin)305 IN_PROC_BROWSER_TEST_F(OobeLocalizationTest, NetworkScreenNonLatin) {
306 // For a non-Latin keyboard layout like Russian, we expect to see the US
307 // keyboard.
308 RunLocalizationTest("ru", "xkb:ru::rus",
309 "ru", kUSLayout,
310 "xkb:us::eng");
311
312 RunLocalizationTest("ru", "xkb:us::eng,xkb:ru::rus",
313 "ru", kUSLayout,
314 "xkb:us::eng");
315
316 // IMEs do not load at OOBE, so we just expect to see the (Latin) Japanese
317 // keyboard.
318 RunLocalizationTest("ja", "xkb:jp::jpn",
319 "ja", "xkb:jp::jpn",
320 "xkb:jp::jpn,[xkb:us::eng]");
321 }
322
IN_PROC_BROWSER_TEST_F(OobeLocalizationTest,NetworkScreenKeyboardLayout)323 IN_PROC_BROWSER_TEST_F(OobeLocalizationTest, NetworkScreenKeyboardLayout) {
324 // We don't use the Icelandic locale but the Icelandic keyboard layout
325 // should still be selected when specified as the default.
326 RunLocalizationTest("en-US", "xkb:is::ice",
327 "en-US", "xkb:is::ice",
328 "xkb:is::ice,["
329 "xkb:us::eng,xkb:us:intl:eng,xkb:us:altgr-intl:eng,"
330 "xkb:us:dvorak:eng,xkb:us:colemak:eng]");
331 }
332
IN_PROC_BROWSER_TEST_F(OobeLocalizationTest,NetworkScreenFullLatin)333 IN_PROC_BROWSER_TEST_F(OobeLocalizationTest, NetworkScreenFullLatin) {
334 // French Swiss keyboard.
335 RunLocalizationTest("fr", "xkb:ch:fr:fra",
336 "fr", "xkb:ch:fr:fra",
337 "xkb:ch:fr:fra,["
338 "xkb:fr::fra,xkb:be::fra,xkb:ca::fra,"
339 "xkb:ca:multix:fra,xkb:us::eng]");
340
341 // German Swiss keyboard.
342 RunLocalizationTest("de", "xkb:ch::ger",
343 "de", "xkb:ch::ger",
344 "xkb:ch::ger,["
345 "xkb:de::ger,xkb:de:neo:ger,xkb:be::ger,xkb:us::eng"
346 "]");
347 }
348
IN_PROC_BROWSER_TEST_F(OobeLocalizationTest,NetworkScreenMultipleLocales)349 IN_PROC_BROWSER_TEST_F(OobeLocalizationTest, NetworkScreenMultipleLocales) {
350 RunLocalizationTest("es,en-US,nl", "xkb:be::nld",
351 "es,en-US,nl", "xkb:be::nld",
352 "xkb:be::nld,[xkb:es::spa,xkb:latam::spa,xkb:us::eng]");
353
354 RunLocalizationTest("ru,de", "xkb:ru::rus",
355 "ru,de", kUSLayout,
356 "xkb:us::eng");
357 }
358
IN_PROC_BROWSER_TEST_F(OobeLocalizationTest,NetworkScreenRegionalLocales)359 IN_PROC_BROWSER_TEST_F(OobeLocalizationTest, NetworkScreenRegionalLocales) {
360 // Syntetic example to test correct merging of different locales.
361 RunLocalizationTest("fr-CH,it-CH,de-CH",
362 "xkb:fr::fra,xkb:it::ita,xkb:de::ger",
363 "fr-CH,it-CH,de-CH",
364 "xkb:fr::fra",
365 "xkb:fr::fra,xkb:it::ita,xkb:de::ger,["
366 "xkb:be::fra,xkb:ca::fra,xkb:ch:fr:fra,"
367 "xkb:ca:multix:fra,xkb:us::eng"
368 "]");
369 // Another syntetic example. Check that british keyboard is available.
370 RunLocalizationTest("en-AU",
371 "xkb:us::eng",
372 "en-AU",
373 "xkb:us::eng",
374 "xkb:us::eng,[xkb:gb:extd:eng,xkb:gb:dvorak:eng]");
375 }
376
377 } // namespace chromeos
378