OLD | NEW |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/browser/chromeos/preferences.h" | 5 #include "chrome/browser/chromeos/preferences.h" |
6 | 6 |
| 7 #include "base/json/json_string_value_serializer.h" |
7 #include "base/prefs/pref_member.h" | 8 #include "base/prefs/pref_member.h" |
| 9 #include "chrome/browser/chromeos/input_method/input_method_configuration.h" |
8 #include "chrome/browser/chromeos/input_method/mock_input_method_manager.h" | 10 #include "chrome/browser/chromeos/input_method/mock_input_method_manager.h" |
9 #include "chrome/browser/chromeos/login/users/fake_user_manager.h" | 11 #include "chrome/browser/chromeos/login/users/fake_user_manager.h" |
10 #include "chrome/browser/chromeos/login/users/user_manager.h" | 12 #include "chrome/browser/chromeos/login/users/user_manager.h" |
| 13 #include "chrome/browser/chromeos/system/fake_input_device_settings.h" |
11 #include "chrome/browser/download/download_prefs.h" | 14 #include "chrome/browser/download/download_prefs.h" |
12 #include "chrome/common/pref_names.h" | 15 #include "chrome/common/pref_names.h" |
13 #include "chrome/test/base/testing_pref_service_syncable.h" | 16 #include "chrome/test/base/testing_pref_service_syncable.h" |
14 #include "components/pref_registry/pref_registry_syncable.h" | 17 #include "components/pref_registry/pref_registry_syncable.h" |
| 18 #include "sync/api/attachments/attachment_id.h" |
| 19 #include "sync/api/attachments/attachment_service_proxy_for_test.h" |
| 20 #include "sync/api/fake_sync_change_processor.h" |
| 21 #include "sync/api/sync_change.h" |
| 22 #include "sync/api/sync_data.h" |
| 23 #include "sync/api/sync_error_factory.h" |
| 24 #include "sync/api/sync_error_factory_mock.h" |
| 25 #include "sync/api/syncable_service.h" |
| 26 #include "sync/protocol/preference_specifics.pb.h" |
| 27 #include "sync/protocol/sync.pb.h" |
15 #include "testing/gtest/include/gtest/gtest.h" | 28 #include "testing/gtest/include/gtest/gtest.h" |
16 | 29 |
17 namespace chromeos { | 30 namespace chromeos { |
18 namespace { | 31 namespace { |
19 | 32 |
| 33 syncer::SyncData |
| 34 CreatePrefSyncData(const std::string& name, const base::Value& value) { |
| 35 std::string serialized; |
| 36 JSONStringValueSerializer json(&serialized); |
| 37 json.Serialize(value); |
| 38 sync_pb::EntitySpecifics specifics; |
| 39 sync_pb::PreferenceSpecifics* pref = specifics.mutable_preference(); |
| 40 pref->set_name(name); |
| 41 pref->set_value(serialized); |
| 42 return syncer::SyncData::CreateRemoteData( |
| 43 1, |
| 44 specifics, |
| 45 base::Time(), |
| 46 syncer::AttachmentIdList(), |
| 47 syncer::AttachmentServiceProxyForTest::Create()); |
| 48 } |
| 49 |
20 class MyMockInputMethodManager : public input_method::MockInputMethodManager { | 50 class MyMockInputMethodManager : public input_method::MockInputMethodManager { |
21 public: | 51 public: |
22 MyMockInputMethodManager(StringPrefMember* previous, | 52 MyMockInputMethodManager(StringPrefMember* previous, |
23 StringPrefMember* current) | 53 StringPrefMember* current) |
24 : previous_(previous), | 54 : previous_(previous), |
25 current_(current) { | 55 current_(current) { |
26 } | 56 } |
27 virtual ~MyMockInputMethodManager() { | 57 virtual ~MyMockInputMethodManager() { |
28 } | 58 } |
29 | 59 |
30 virtual void ChangeInputMethod(const std::string& input_method_id) OVERRIDE { | 60 virtual void ChangeInputMethod(const std::string& input_method_id) OVERRIDE { |
31 last_input_method_id_ = input_method_id; | 61 last_input_method_id_ = input_method_id; |
32 // Do the same thing as BrowserStateMonitor::UpdateUserPreferences. | 62 // Do the same thing as BrowserStateMonitor::UpdateUserPreferences. |
33 const std::string current_input_method_on_pref = current_->GetValue(); | 63 const std::string current_input_method_on_pref = current_->GetValue(); |
34 if (current_input_method_on_pref == input_method_id) | 64 if (current_input_method_on_pref == input_method_id) |
35 return; | 65 return; |
36 previous_->SetValue(current_input_method_on_pref); | 66 previous_->SetValue(current_input_method_on_pref); |
37 current_->SetValue(input_method_id); | 67 current_->SetValue(input_method_id); |
38 } | 68 } |
39 | 69 |
40 std::string last_input_method_id_; | 70 std::string last_input_method_id_; |
41 | 71 |
42 private: | 72 private: |
43 StringPrefMember* previous_; | 73 StringPrefMember* previous_; |
44 StringPrefMember* current_; | 74 StringPrefMember* current_; |
45 }; | 75 }; |
46 | 76 |
47 } // anonymous namespace | 77 } // anonymous namespace |
48 | 78 |
49 TEST(PreferencesTest, TestUpdatePrefOnBrowserScreenDetails) { | 79 class PreferencesTest : public testing::Test { |
50 chromeos::FakeUserManager* user_manager = new chromeos::FakeUserManager(); | 80 public: |
51 chromeos::ScopedUserManagerEnabler user_manager_enabler(user_manager); | 81 PreferencesTest() {} |
52 const char test_user_email[] = "test_user@example.com"; | 82 virtual ~PreferencesTest() {} |
53 const User* test_user = user_manager->AddUser(test_user_email); | 83 |
54 user_manager->LoginUser(test_user_email); | 84 virtual void SetUp() OVERRIDE { |
55 | 85 chromeos::FakeUserManager* user_manager = new chromeos::FakeUserManager(); |
56 TestingPrefServiceSyncable prefs; | 86 user_manager_enabler_.reset( |
57 Preferences::RegisterProfilePrefs(prefs.registry()); | 87 new chromeos::ScopedUserManagerEnabler(user_manager)); |
58 DownloadPrefs::RegisterProfilePrefs(prefs.registry()); | 88 |
59 // kSelectFileLastDirectory is registered for Profile. Here we register it for | 89 const char test_user_email[] = "test_user@example.com"; |
60 // testing. | 90 test_user_ = user_manager->AddUser(test_user_email); |
61 prefs.registry()->RegisterStringPref( | 91 user_manager->LoginUser(test_user_email); |
62 prefs::kSelectFileLastDirectory, | 92 user_manager->SwitchActiveUser(test_user_email); |
63 std::string(), | 93 |
64 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); | 94 pref_service_.reset(new TestingPrefServiceSyncable); |
65 | 95 Preferences::RegisterProfilePrefs(pref_service_->registry()); |
66 StringPrefMember previous; | 96 DownloadPrefs::RegisterProfilePrefs(pref_service_->registry()); |
67 previous.Init(prefs::kLanguagePreviousInputMethod, &prefs); | 97 |
68 previous.SetValue("KeyboardA"); | 98 // kSelectFileLastDirectory is registered for Profile. Here we register it |
69 StringPrefMember current; | 99 // for testing. |
70 current.Init(prefs::kLanguageCurrentInputMethod, &prefs); | 100 pref_service_->registry()->RegisterStringPref( |
71 current.SetValue("KeyboardB"); | 101 prefs::kSelectFileLastDirectory, |
72 | 102 std::string(), |
73 MyMockInputMethodManager mock_manager(&previous, ¤t); | 103 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); |
74 Preferences testee(&mock_manager); | 104 |
75 testee.InitUserPrefsForTesting(&prefs, test_user); | 105 previous_input_method_.Init( |
76 testee.SetInputMethodListForTesting(); | 106 prefs::kLanguagePreviousInputMethod, pref_service_.get()); |
77 | 107 previous_input_method_.SetValue("KeyboardA"); |
78 // Confirm they're unchanged. | 108 current_input_method_.Init( |
79 EXPECT_EQ("KeyboardA", previous.GetValue()); | 109 prefs::kLanguageCurrentInputMethod, pref_service_.get()); |
80 EXPECT_EQ("KeyboardB", current.GetValue()); | 110 current_input_method_.SetValue("KeyboardB"); |
81 EXPECT_EQ("KeyboardB", mock_manager.last_input_method_id_); | 111 |
| 112 mock_manager_ = new MyMockInputMethodManager(&previous_input_method_, |
| 113 ¤t_input_method_); |
| 114 input_method::InitializeForTesting(mock_manager_); |
| 115 system::InputDeviceSettings::SetSettingsForTesting( |
| 116 new system::FakeInputDeviceSettings()); |
| 117 prefs_.reset(new Preferences(mock_manager_)); |
| 118 } |
| 119 |
| 120 virtual void TearDown() OVERRIDE { |
| 121 input_method::Shutdown(); |
| 122 } |
| 123 |
| 124 scoped_ptr<chromeos::ScopedUserManagerEnabler> user_manager_enabler_; |
| 125 const User* test_user_; |
| 126 scoped_ptr<TestingPrefServiceSyncable> pref_service_; |
| 127 StringPrefMember previous_input_method_; |
| 128 StringPrefMember current_input_method_; |
| 129 MyMockInputMethodManager* mock_manager_; |
| 130 scoped_ptr<Preferences> prefs_; |
| 131 |
| 132 private: |
| 133 DISALLOW_COPY_AND_ASSIGN(PreferencesTest); |
| 134 }; |
| 135 |
| 136 TEST_F(PreferencesTest, TestUpdatePrefOnBrowserScreenDetails) { |
| 137 prefs_->Init(pref_service_.get(), test_user_); |
| 138 |
| 139 // Confirm the current and previous input methods are unchanged. |
| 140 EXPECT_EQ("KeyboardA", previous_input_method_.GetValue()); |
| 141 EXPECT_EQ("KeyboardB", current_input_method_.GetValue()); |
| 142 EXPECT_EQ("KeyboardB", mock_manager_->last_input_method_id_); |
| 143 } |
| 144 |
| 145 class LocalePreferencesTest : public PreferencesTest { |
| 146 public: |
| 147 LocalePreferencesTest() {} |
| 148 virtual ~LocalePreferencesTest() {} |
| 149 |
| 150 virtual void SetUp() OVERRIDE { |
| 151 PreferencesTest::SetUp(); |
| 152 |
| 153 preferred_languages_.Init(prefs::kLanguagePreferredLanguages, |
| 154 pref_service_.get()); |
| 155 preferred_languages_local_.Init(prefs::kLanguagePreferredLanguagesLocal, |
| 156 pref_service_.get()); |
| 157 preload_engines_.Init(prefs::kLanguagePreloadEngines, pref_service_.get()); |
| 158 preload_engines_local_.Init(prefs::kLanguagePreloadEnginesLocal, |
| 159 pref_service_.get()); |
| 160 enabled_extension_imes_.Init(prefs::kLanguageEnabledExtensionImes, |
| 161 pref_service_.get()); |
| 162 enabled_extension_imes_local_.Init( |
| 163 prefs::kLanguageEnabledExtensionImesLocal, pref_service_.get()); |
| 164 } |
| 165 |
| 166 // Helper function to set local language and input values. |
| 167 void SetLocalValues(const std::string& preferred_languages, |
| 168 const std::string& preload_engines, |
| 169 const std::string& enabled_extension_imes) { |
| 170 preferred_languages_local_.SetValue(preferred_languages); |
| 171 preload_engines_local_.SetValue(preload_engines); |
| 172 enabled_extension_imes_local_.SetValue(enabled_extension_imes); |
| 173 } |
| 174 |
| 175 // Helper function to set global language and input values. |
| 176 void SetGlobalValues(const std::string& preferred_languages, |
| 177 const std::string& preload_engines, |
| 178 const std::string& enabled_extension_imes) { |
| 179 preferred_languages_.SetValue(preferred_languages); |
| 180 preload_engines_.SetValue(preload_engines); |
| 181 enabled_extension_imes_.SetValue(enabled_extension_imes); |
| 182 } |
| 183 |
| 184 // Helper function to check local language and input values. |
| 185 void ExpectLocalValues(const std::string& preferred_languages, |
| 186 const std::string& preload_engines, |
| 187 const std::string& enabled_extension_imes) { |
| 188 EXPECT_EQ(preferred_languages, preferred_languages_local_.GetValue()); |
| 189 EXPECT_EQ(preload_engines, preload_engines_local_.GetValue()); |
| 190 EXPECT_EQ(enabled_extension_imes, enabled_extension_imes_local_.GetValue()); |
| 191 } |
| 192 |
| 193 // Helper function to check global language and input values. |
| 194 void ExpectGlobalValues(const std::string& preferred_languages, |
| 195 const std::string& preload_engines, |
| 196 const std::string& enabled_extension_imes) { |
| 197 EXPECT_EQ(preferred_languages, preferred_languages_.GetValue()); |
| 198 EXPECT_EQ(preload_engines, preload_engines_.GetValue()); |
| 199 EXPECT_EQ(enabled_extension_imes, enabled_extension_imes_.GetValue()); |
| 200 } |
| 201 |
| 202 StringPrefMember preferred_languages_; |
| 203 StringPrefMember preferred_languages_local_; |
| 204 StringPrefMember preload_engines_; |
| 205 StringPrefMember preload_engines_local_; |
| 206 StringPrefMember enabled_extension_imes_; |
| 207 StringPrefMember enabled_extension_imes_local_; |
| 208 |
| 209 private: |
| 210 DISALLOW_COPY_AND_ASSIGN(LocalePreferencesTest); |
| 211 }; |
| 212 |
| 213 TEST_F(LocalePreferencesTest, TestOOBEAndSync) { |
| 214 // Choose options at OOBE. |
| 215 SetLocalValues("es", "xkb:es", ""); |
| 216 |
| 217 // Initialize preferences. |
| 218 prefs_->Init(pref_service_.get(), test_user_); |
| 219 |
| 220 // Add an input method before syncing starts. |
| 221 preload_engines_local_.SetValue("xkb:es,xkb:us"); |
| 222 |
| 223 // Create some values to come from the server. |
| 224 syncer::SyncDataList sync_data_list; |
| 225 sync_data_list.push_back(CreatePrefSyncData( |
| 226 prefs::kLanguagePreferredLanguages, base::StringValue("ru,fi"))); |
| 227 sync_data_list.push_back(CreatePrefSyncData( |
| 228 prefs::kLanguagePreloadEngines, base::StringValue("xkb:ru"))); |
| 229 sync_data_list.push_back(CreatePrefSyncData( |
| 230 prefs::kLanguageEnabledExtensionImes, base::StringValue("ime1"))); |
| 231 |
| 232 // Sync for the first time. |
| 233 syncer::SyncableService* sync = |
| 234 pref_service_->GetSyncableService(syncer::PREFERENCES); |
| 235 sync->MergeDataAndStartSyncing(syncer::PREFERENCES, |
| 236 sync_data_list, |
| 237 scoped_ptr<syncer::SyncChangeProcessor>( |
| 238 new syncer::FakeSyncChangeProcessor), |
| 239 scoped_ptr<syncer::SyncErrorFactory>( |
| 240 new syncer::SyncErrorFactoryMock)); |
| 241 { |
| 242 SCOPED_TRACE("Server values should have merged with local values."); |
| 243 ExpectLocalValues("es,ru,fi", "xkb:es,xkb:us,xkb:ru", "ime1"); |
| 244 } |
| 245 |
| 246 // Update the global values from the server again. |
| 247 syncer::SyncChangeList change_list; |
| 248 change_list.push_back(syncer::SyncChange( |
| 249 FROM_HERE, |
| 250 syncer::SyncChange::ACTION_UPDATE, |
| 251 CreatePrefSyncData( |
| 252 prefs::kLanguagePreferredLanguages, |
| 253 base::StringValue("de")))); |
| 254 change_list.push_back(syncer::SyncChange( |
| 255 FROM_HERE, |
| 256 syncer::SyncChange::ACTION_UPDATE, |
| 257 CreatePrefSyncData( |
| 258 prefs::kLanguagePreloadEngines, |
| 259 base::StringValue("xkb:de")))); |
| 260 change_list.push_back(syncer::SyncChange( |
| 261 FROM_HERE, |
| 262 syncer::SyncChange::ACTION_UPDATE, |
| 263 CreatePrefSyncData( |
| 264 prefs::kLanguageEnabledExtensionImes, |
| 265 base::StringValue("ime2")))); |
| 266 sync->ProcessSyncChanges(FROM_HERE, change_list); |
| 267 |
| 268 { |
| 269 SCOPED_TRACE("Local preferences should have remained the same."); |
| 270 ExpectLocalValues("es,ru,fi", "xkb:es,xkb:us,xkb:ru", "ime1"); |
| 271 } |
| 272 { |
| 273 // Change local preferences. |
| 274 SCOPED_TRACE("Global preferences should have been updated."); |
| 275 SetLocalValues("jp", "xkb:jp", "ime2"); |
| 276 ExpectGlobalValues("jp", "xkb:jp", "ime2"); |
| 277 } |
| 278 } |
| 279 |
| 280 TEST_F(LocalePreferencesTest, TestLogIn) { |
| 281 // Set up existing preference values. |
| 282 SetLocalValues("es", "xkb:es", "ime1"); |
| 283 SetGlobalValues("es", "xkb:es", "ime1"); |
| 284 |
| 285 // Initialize preferences. |
| 286 prefs_->Init(pref_service_.get(), test_user_); |
| 287 |
| 288 // Create some values to come from the server. |
| 289 syncer::SyncDataList sync_data_list; |
| 290 sync_data_list.push_back(CreatePrefSyncData( |
| 291 prefs::kLanguagePreferredLanguages, base::StringValue("ru,fi"))); |
| 292 sync_data_list.push_back(CreatePrefSyncData( |
| 293 prefs::kLanguagePreloadEngines, base::StringValue("xkb:ru"))); |
| 294 sync_data_list.push_back(CreatePrefSyncData( |
| 295 prefs::kLanguageEnabledExtensionImes, base::StringValue("ime1"))); |
| 296 |
| 297 // Sync. |
| 298 syncer::SyncableService* sync = |
| 299 pref_service_->GetSyncableService(syncer::PREFERENCES); |
| 300 sync->MergeDataAndStartSyncing(syncer::PREFERENCES, |
| 301 sync_data_list, |
| 302 scoped_ptr<syncer::SyncChangeProcessor>( |
| 303 new syncer::FakeSyncChangeProcessor), |
| 304 scoped_ptr<syncer::SyncErrorFactory>( |
| 305 new syncer::SyncErrorFactoryMock)); |
| 306 { |
| 307 SCOPED_TRACE("Local preferences should have remained the same."); |
| 308 ExpectLocalValues("es", "xkb:es", "ime1"); |
| 309 } |
| 310 { |
| 311 // Change local preferences. |
| 312 SCOPED_TRACE("Global preferences should have been updated."); |
| 313 SetLocalValues("jp", "xkb:jp", "ime2"); |
| 314 ExpectGlobalValues("jp", "xkb:jp", "ime2"); |
| 315 } |
| 316 } |
| 317 |
| 318 TEST_F(LocalePreferencesTest, TestLogInDifferentGlobalValues) { |
| 319 // Set up existing preferences. |
| 320 SetLocalValues("es", "xkb:es", "ime1"); |
| 321 |
| 322 // The global preferences have changed since we initialized the local prefs. |
| 323 SetGlobalValues("ru", "xkb:ru", "ime2"); |
| 324 |
| 325 // Initialize preferences. |
| 326 prefs_->Init(pref_service_.get(), test_user_); |
| 327 { |
| 328 SCOPED_TRACE("Local preferences should have remained the same."); |
| 329 ExpectLocalValues("es", "xkb:es", "ime1"); |
| 330 } |
| 331 { |
| 332 // Change local preferences. |
| 333 SCOPED_TRACE("Global preferences should have been updated."); |
| 334 SetLocalValues("jp", "xkb:jp", "ime3"); |
| 335 ExpectGlobalValues("jp", "xkb:jp", "ime3"); |
| 336 } |
| 337 } |
| 338 |
| 339 TEST_F(LocalePreferencesTest, TestLogInLegacy) { |
| 340 // Simulate existing local preferences from M-36. |
| 341 SetGlobalValues("es", "xkb:es", "ime1"); |
| 342 |
| 343 // Initialize preferences. |
| 344 prefs_->Init(pref_service_.get(), test_user_); |
| 345 { |
| 346 SCOPED_TRACE("Local preferences should have been set from global values."); |
| 347 ExpectLocalValues("es", "xkb:es", "ime1"); |
| 348 } |
| 349 |
| 350 // Sync. Since this is an existing profile, the local values don't change. |
| 351 syncer::SyncDataList sync_data_list; |
| 352 sync_data_list.push_back(CreatePrefSyncData( |
| 353 prefs::kLanguagePreferredLanguages, base::StringValue("ru,fi"))); |
| 354 sync_data_list.push_back(CreatePrefSyncData( |
| 355 prefs::kLanguagePreloadEngines, base::StringValue("xkb:ru"))); |
| 356 sync_data_list.push_back(CreatePrefSyncData( |
| 357 prefs::kLanguageEnabledExtensionImes, base::StringValue("ime2"))); |
| 358 |
| 359 syncer::SyncableService* sync = |
| 360 pref_service_->GetSyncableService(syncer::PREFERENCES); |
| 361 sync->MergeDataAndStartSyncing(syncer::PREFERENCES, |
| 362 sync_data_list, |
| 363 scoped_ptr<syncer::SyncChangeProcessor>( |
| 364 new syncer::FakeSyncChangeProcessor), |
| 365 scoped_ptr<syncer::SyncErrorFactory>( |
| 366 new syncer::SyncErrorFactoryMock)); |
| 367 { |
| 368 SCOPED_TRACE("Local preferences should have remained the same."); |
| 369 ExpectLocalValues("es", "xkb:es", "ime1"); |
| 370 } |
| 371 { |
| 372 // Change local preferences. |
| 373 SCOPED_TRACE("Global preferences should have been updated."); |
| 374 SetLocalValues("jp", "xkb:jp", "ime3"); |
| 375 ExpectGlobalValues("jp", "xkb:jp", "ime3"); |
| 376 } |
| 377 } |
| 378 |
| 379 TEST_F(LocalePreferencesTest, MergeStressTest) { |
| 380 SetLocalValues("es", "xkb:es,xkb:us,xkb:jp", ""); |
| 381 |
| 382 // Initialize preferences. |
| 383 prefs_->Init(pref_service_.get(), test_user_); |
| 384 |
| 385 // Change input methods and languages before syncing starts. |
| 386 SetLocalValues("en,es,jp,ar", |
| 387 "xkb:es,xkb:jp,xkb:dv,xkb:ar", |
| 388 "ime2,ime1,ime4"); |
| 389 |
| 390 // Create some tricky values to come from the server. |
| 391 syncer::SyncDataList sync_data_list; |
| 392 sync_data_list.push_back(CreatePrefSyncData( |
| 393 prefs::kLanguagePreferredLanguages, base::StringValue("ar,fi,es,de,ar"))); |
| 394 sync_data_list.push_back(CreatePrefSyncData( |
| 395 prefs::kLanguagePreloadEngines, |
| 396 base::StringValue("xkb:ru,xkb:ru,xkb:jp,xkb:jp"))); |
| 397 sync_data_list.push_back(CreatePrefSyncData( |
| 398 prefs::kLanguageEnabledExtensionImes, |
| 399 base::StringValue(""))); |
| 400 |
| 401 // Sync for the first time. |
| 402 syncer::SyncableService* sync = |
| 403 pref_service_->GetSyncableService(syncer::PREFERENCES); |
| 404 sync->MergeDataAndStartSyncing(syncer::PREFERENCES, |
| 405 sync_data_list, |
| 406 scoped_ptr<syncer::SyncChangeProcessor>( |
| 407 new syncer::FakeSyncChangeProcessor), |
| 408 scoped_ptr<syncer::SyncErrorFactory>( |
| 409 new syncer::SyncErrorFactoryMock)); |
| 410 { |
| 411 SCOPED_TRACE("Server values should have merged with local values."); |
| 412 ExpectLocalValues("en,es,jp,ar,fi,de", |
| 413 "xkb:es,xkb:jp,xkb:dv,xkb:ar,xkb:ru", |
| 414 "ime2,ime1,ime4"); |
| 415 } |
82 } | 416 } |
83 | 417 |
84 } // namespace chromeos | 418 } // namespace chromeos |
OLD | NEW |