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: 48 virtual ~FakeStatisticsProvider() {} 49 50 void set_locale(const std::string& locale) { 51 initial_locale_ = locale; 52 } 53 54 void set_keyboard_layout(const std::string& keyboard_layout) { 55 keyboard_layout_ = keyboard_layout; 56 } 57 58 private: 59 // StatisticsProvider overrides. 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. 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 79 virtual bool GetMachineFlag(const std::string& name, bool* result) OVERRIDE { 80 return false; 81 } 82 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 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 131 OobeLocalizationTest::~OobeLocalizationTest() { 132 system::StatisticsProvider::SetTestProvider(NULL); 133 } 134 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 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 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 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 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 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 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 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 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 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