|  | // Copyright 2014 The Chromium Authors | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #ifdef UNSAFE_BUFFERS_BUILD | 
|  | // TODO(crbug.com/40285824): Remove this and convert code to safer constructs. | 
|  | #pragma allow_unsafe_buffers | 
|  | #endif | 
|  |  | 
|  | #include "chrome/browser/ash/customization/customization_document.h" | 
|  |  | 
|  | #include <stddef.h> | 
|  |  | 
|  | #include "base/command_line.h" | 
|  | #include "base/functional/bind.h" | 
|  | #include "base/strings/string_split.h" | 
|  | #include "base/strings/string_util.h" | 
|  | #include "base/threading/thread_restrictions.h" | 
|  | #include "chrome/browser/ash/base/locale_util.h" | 
|  | #include "chrome/browser/browser_process.h" | 
|  | #include "chrome/browser/profiles/profile_manager.h" | 
|  | #include "chrome/browser/ui/webui/ash/login/l10n_util.h" | 
|  | #include "chrome/test/base/in_process_browser_test.h" | 
|  | #include "chromeos/ash/components/system/fake_statistics_provider.h" | 
|  | #include "chromeos/ash/components/system/statistics_provider.h" | 
|  | #include "content/public/test/browser_test.h" | 
|  | #include "content/public/test/test_utils.h" | 
|  | #include "testing/gmock/include/gmock/gmock.h" | 
|  | #include "testing/gtest/include/gtest/gtest.h" | 
|  | #include "ui/base/l10n/l10n_util.h" | 
|  |  | 
|  | namespace ash { | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | using locale_util::LanguageSwitchResult; | 
|  | using locale_util::SwitchLanguageCallback; | 
|  | using ::testing::Optional; | 
|  | using ::testing::StrEq; | 
|  |  | 
|  | class LanguageSwitchedWaiter { | 
|  | public: | 
|  | explicit LanguageSwitchedWaiter(SwitchLanguageCallback callback) | 
|  | : callback_(std::move(callback)), | 
|  | finished_(false), | 
|  | runner_(new content::MessageLoopRunner) {} | 
|  |  | 
|  | LanguageSwitchedWaiter(const LanguageSwitchedWaiter&) = delete; | 
|  | LanguageSwitchedWaiter& operator=(const LanguageSwitchedWaiter&) = delete; | 
|  |  | 
|  | void ExitMessageLoop(const LanguageSwitchResult& result) { | 
|  | finished_ = true; | 
|  | runner_->Quit(); | 
|  | std::move(callback_).Run(result); | 
|  | } | 
|  |  | 
|  | void Wait() { | 
|  | if (finished_) | 
|  | return; | 
|  | runner_->Run(); | 
|  | } | 
|  |  | 
|  | SwitchLanguageCallback Callback() { | 
|  | return SwitchLanguageCallback(base::BindOnce( | 
|  | &LanguageSwitchedWaiter::ExitMessageLoop, base::Unretained(this))); | 
|  | } | 
|  |  | 
|  | private: | 
|  | SwitchLanguageCallback callback_; | 
|  | bool finished_; | 
|  | scoped_refptr<content::MessageLoopRunner> runner_; | 
|  | }; | 
|  |  | 
|  | const struct { | 
|  | const char* locale_alias; | 
|  | const char* locale_name; | 
|  | } locale_aliases[] = {{"en-AU", "en-GB"}, | 
|  | {"en-CA", "en-GB"}, | 
|  | {"en-NZ", "en-GB"}, | 
|  | {"en-ZA", "en-GB"}, | 
|  | {"fr-CA", "fr"}, | 
|  | {"no", "nb"}, | 
|  | {"iw", "he"}}; | 
|  |  | 
|  | // Several language IDs are actually aliases to another IDs, so real language | 
|  | // ID is reported as "loaded" when alias is requested. | 
|  | std::string GetExpectedLanguage(const std::string& required) { | 
|  | std::string expected = required; | 
|  |  | 
|  | for (size_t i = 0; i < std::size(locale_aliases); ++i) { | 
|  | if (required != locale_aliases[i].locale_alias) | 
|  | continue; | 
|  |  | 
|  | expected = locale_aliases[i].locale_name; | 
|  | break; | 
|  | } | 
|  |  | 
|  | return expected; | 
|  | } | 
|  |  | 
|  | void VerifyLanguageSwitched(const LanguageSwitchResult& result) { | 
|  | EXPECT_TRUE(result.success) << "SwitchLanguage failed: required='" | 
|  | << result.requested_locale << "', actual='" | 
|  | << result.loaded_locale | 
|  | << "', success=" << result.success; | 
|  | EXPECT_EQ(GetExpectedLanguage(result.requested_locale), result.loaded_locale) | 
|  | << "SwitchLanguage failed: required='" << result.requested_locale | 
|  | << "', actual='" << result.loaded_locale | 
|  | << "', success=" << result.success; | 
|  | } | 
|  |  | 
|  | std::string Print(const std::vector<std::string>& locales) { | 
|  | std::string result("{"); | 
|  | for (size_t i = 0; i < locales.size(); ++i) { | 
|  | if (i != 0) { | 
|  | result += ", "; | 
|  | } | 
|  | result += "'"; | 
|  | result += locales[i]; | 
|  | result += "'"; | 
|  | } | 
|  | result += "}"; | 
|  | return result; | 
|  | } | 
|  |  | 
|  | const char* kVPDInitialLocales[] = { | 
|  | "ar", | 
|  | "ar,bg", | 
|  | "ar,bg,bn", | 
|  | "ar,bg,bn,ca", | 
|  | "ar,bg,bn,ca,cs,da,de,el,en-AU,en-CA,en-GB,en-NZ,en-US,en-ZA,es,es-419,et," | 
|  | "fa,fi,fil,fr,fr-CA,gu,he,hi,hr,hu,id,it,ja,kn,ko,lt,lv,ml,mr,ms,nl,nb,pl," | 
|  | "pt-BR,pt-PT,ro,ru,sk,sl,sr,sv,ta,te,th,tr,vi,zh-CN,zh-TW", | 
|  | }; | 
|  |  | 
|  | const std::vector<std::string> languages_available = { | 
|  | "ar", | 
|  | "bg", | 
|  | "bn", | 
|  | "ca", | 
|  | "cs", | 
|  | "da", | 
|  | "de", | 
|  | "el", | 
|  | "en-AU", | 
|  | "en-CA", | 
|  | "en-GB", | 
|  | "en-NZ", | 
|  | "en-US", | 
|  | "en-ZA", | 
|  | "es", | 
|  | "es-419", | 
|  | "et", | 
|  | "fa", | 
|  | "fi", | 
|  | "fil", | 
|  | "fr", | 
|  | "fr-CA", | 
|  | "gu", | 
|  | "he", | 
|  | "hi", | 
|  | "hr", | 
|  | "hu", | 
|  | "id", | 
|  | "it", | 
|  | "ja", | 
|  | "kn", | 
|  | "ko", | 
|  | "lt", | 
|  | "lv", | 
|  | "ml", | 
|  | "mr", | 
|  | "ms", | 
|  | "nl", | 
|  | "nb", | 
|  | "pl", | 
|  | "pt-BR", | 
|  | "pt-PT", | 
|  | "ro", | 
|  | "ru", | 
|  | "sk", | 
|  | "sl", | 
|  | "sr", | 
|  | "sv", | 
|  | "ta", | 
|  | "te", | 
|  | "th", | 
|  | "tr", | 
|  | "vi", | 
|  | "zh-CN", | 
|  | "zh-TW" | 
|  | }; | 
|  |  | 
|  | }  // anonymous namespace | 
|  |  | 
|  | typedef InProcessBrowserTest CustomizationLocaleTest; | 
|  |  | 
|  | IN_PROC_BROWSER_TEST_F(CustomizationLocaleTest, CheckAvailableLocales) { | 
|  | for (size_t i = 0; i < languages_available.size(); ++i) { | 
|  | LanguageSwitchedWaiter waiter(base::BindOnce(&VerifyLanguageSwitched)); | 
|  | locale_util::SwitchLanguage(languages_available[i], true, true, | 
|  | waiter.Callback(), | 
|  | ProfileManager::GetActiveUserProfile()); | 
|  | waiter.Wait(); | 
|  | { | 
|  | base::ScopedAllowBlockingForTesting allow_blocking; | 
|  | EXPECT_THAT(l10n_util::CheckAndResolveLocale(languages_available[i]), | 
|  | Optional(StrEq(GetExpectedLanguage(languages_available[i])))) | 
|  | << "CheckAndResolveLocale() failed for language='" | 
|  | << languages_available[i] << "'"; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | class CustomizationVPDTest : public InProcessBrowserTest, | 
|  | public testing::WithParamInterface<const char*> { | 
|  | public: | 
|  | CustomizationVPDTest() | 
|  | : statistics_provider_(new system::FakeStatisticsProvider()) { | 
|  | // Set the instance returned by GetInstance() for testing. | 
|  | system::StatisticsProvider::SetTestProvider(statistics_provider_.get()); | 
|  | statistics_provider_->SetMachineStatistic("initial_locale", GetParam()); | 
|  | statistics_provider_->SetMachineStatistic("keyboard_layout", ""); | 
|  | statistics_provider_->SetVpdStatus( | 
|  | system::StatisticsProvider::VpdStatus::kValid); | 
|  | } | 
|  |  | 
|  | private: | 
|  | std::unique_ptr<system::FakeStatisticsProvider> statistics_provider_; | 
|  | }; | 
|  |  | 
|  | IN_PROC_BROWSER_TEST_P(CustomizationVPDTest, GetUILanguageList) { | 
|  | std::vector<std::string> locales = base::SplitString( | 
|  | GetParam(), ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); | 
|  |  | 
|  | for (std::string& l : locales) { | 
|  | base::TrimString(l, " ", &l); | 
|  | } | 
|  | EXPECT_EQ(locales, | 
|  | StartupCustomizationDocument::GetInstance()->configured_locales()) | 
|  | << "Test failed for initial_locale='" << GetParam() | 
|  | << "', locales=" << Print(locales); | 
|  |  | 
|  | auto ui_language_list = | 
|  | GetUILanguageList(g_browser_process->GetApplicationLocale(), nullptr, "", | 
|  | input_method::InputMethodManager::Get()); | 
|  | EXPECT_GE(ui_language_list.size(), locales.size()) | 
|  | << "Test failed for initial_locale='" << GetParam() << "'"; | 
|  |  | 
|  | for (size_t i = 0; i < ui_language_list.size(); ++i) { | 
|  | base::Value::Dict* language_info = ui_language_list[i].GetIfDict(); | 
|  |  | 
|  | ASSERT_TRUE(language_info) | 
|  | << "Test failed for initial_locale='" << GetParam() << "', i=" << i; | 
|  |  | 
|  | std::string* value = language_info->FindString("value"); | 
|  | ASSERT_TRUE(value) << "Test failed for initial_locale='" << GetParam() | 
|  | << "', i=" << i; | 
|  |  | 
|  | if (i < locales.size()) { | 
|  | EXPECT_EQ(locales[i], *value) | 
|  | << "Test failed for initial_locale='" << GetParam() << "', i=" << i; | 
|  | } else { | 
|  | EXPECT_EQ(kMostRelevantLanguagesDivider, *value) | 
|  | << "Test failed for initial_locale='" << GetParam() << "', i=" << i; | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | INSTANTIATE_TEST_SUITE_P(StringSequence, | 
|  | CustomizationVPDTest, | 
|  | testing::ValuesIn(kVPDInitialLocales)); | 
|  |  | 
|  | }  // namespace ash |