diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-10-12 14:27:29 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-10-13 09:35:20 +0000 |
commit | c30a6232df03e1efbd9f3b226777b07e087a1122 (patch) | |
tree | e992f45784689f373bcc38d1b79a239ebe17ee23 /chromium/chrome/browser/spellchecker | |
parent | 7b5b123ac58f58ffde0f4f6e488bcd09aa4decd3 (diff) | |
download | qtwebengine-chromium-85-based.tar.gz |
BASELINE: Update Chromium to 85.0.4183.14085-based
Change-Id: Iaa42f4680837c57725b1344f108c0196741f6057
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
Diffstat (limited to 'chromium/chrome/browser/spellchecker')
14 files changed, 1078 insertions, 166 deletions
diff --git a/chromium/chrome/browser/spellchecker/spell_check_host_chrome_impl.cc b/chromium/chrome/browser/spellchecker/spell_check_host_chrome_impl.cc index bce592b0662..aa5b7ab1095 100644 --- a/chromium/chrome/browser/spellchecker/spell_check_host_chrome_impl.cc +++ b/chromium/chrome/browser/spellchecker/spell_check_host_chrome_impl.cc @@ -194,7 +194,7 @@ void SpellCheckHostChromeImpl::RequestTextCheck( base::Unretained(this)))); } -#if BUILDFLAG(USE_WIN_HYBRID_SPELLCHECKER) +#if defined(OS_WIN) void SpellCheckHostChromeImpl::GetPerLanguageSuggestions( const base::string16& word, GetPerLanguageSuggestionsCallback callback) { @@ -208,7 +208,7 @@ void SpellCheckHostChromeImpl::GetPerLanguageSuggestions( spellcheck_platform::GetPerLanguageSuggestions( spellcheck->platform_spell_checker(), word, std::move(callback)); } -#endif // BUILDFLAG(USE_WIN_HYBRID_SPELLCHECKER) +#endif // defined(OS_WIN) void SpellCheckHostChromeImpl::OnRequestFinished(SpellingRequest* request) { auto iterator = requests_.find(request); diff --git a/chromium/chrome/browser/spellchecker/spell_check_host_chrome_impl.h b/chromium/chrome/browser/spellchecker/spell_check_host_chrome_impl.h index 984b852311b..17bb3d80a82 100644 --- a/chromium/chrome/browser/spellchecker/spell_check_host_chrome_impl.h +++ b/chromium/chrome/browser/spellchecker/spell_check_host_chrome_impl.h @@ -79,11 +79,11 @@ class SpellCheckHostChromeImpl : public SpellCheckHostImpl { int route_id, RequestTextCheckCallback callback) override; -#if BUILDFLAG(USE_WIN_HYBRID_SPELLCHECKER) +#if defined(OS_WIN) void GetPerLanguageSuggestions( const base::string16& word, GetPerLanguageSuggestionsCallback callback) override; -#endif // BUILDFLAG(USE_WIN_HYBRID_SPELLCHECKER) +#endif // defined(OS_WIN) // Clears a finished request from |requests_|. Exposed to SpellingRequest. void OnRequestFinished(SpellingRequest* request); diff --git a/chromium/chrome/browser/spellchecker/spell_check_host_chrome_impl_win_browsertest.cc b/chromium/chrome/browser/spellchecker/spell_check_host_chrome_impl_win_browsertest.cc index d838e7028bc..6c8810f7135 100644 --- a/chromium/chrome/browser/spellchecker/spell_check_host_chrome_impl_win_browsertest.cc +++ b/chromium/chrome/browser/spellchecker/spell_check_host_chrome_impl_win_browsertest.cc @@ -28,14 +28,7 @@ class PlatformSpellChecker; class SpellCheckHostChromeImplWinBrowserTest : public InProcessBrowserTest { public: SpellCheckHostChromeImplWinBrowserTest() { -#if BUILDFLAG(USE_WIN_HYBRID_SPELLCHECKER) - feature_list_.InitWithFeatures( - /*enabled_features=*/{spellcheck::kWinUseBrowserSpellChecker, - spellcheck::kWinUseHybridSpellChecker}, - /*disabled_features=*/{}); -#else feature_list_.InitAndEnableFeature(spellcheck::kWinUseBrowserSpellChecker); -#endif // BUILDFLAG(USE_WIN_HYBRID_SPELLCHECKER) } void SetUpOnMainThread() override { @@ -123,7 +116,6 @@ IN_PROC_BROWSER_TEST_F(SpellCheckHostChromeImplWinBrowserTest, EXPECT_EQ(result_[0].decoration, SpellCheckResult::SPELLING); } -#if BUILDFLAG(USE_WIN_HYBRID_SPELLCHECKER) IN_PROC_BROWSER_TEST_F(SpellCheckHostChromeImplWinBrowserTest, GetPerLanguageSuggestions) { if (!spellcheck::WindowsVersionSupportsSpellchecker()) { @@ -148,4 +140,3 @@ IN_PROC_BROWSER_TEST_F(SpellCheckHostChromeImplWinBrowserTest, ASSERT_EQ(1U, suggestion_result_.size()); EXPECT_GT(suggestion_result_[0].size(), 0U); } -#endif // BUILDFLAG(USE_WIN_HYBRID_SPELLCHECKER) diff --git a/chromium/chrome/browser/spellchecker/spellcheck_custom_dictionary.cc b/chromium/chrome/browser/spellchecker/spellcheck_custom_dictionary.cc index cdda080ce7f..582654c85e6 100644 --- a/chromium/chrome/browser/spellchecker/spellcheck_custom_dictionary.cc +++ b/chromium/chrome/browser/spellchecker/spellcheck_custom_dictionary.cc @@ -453,10 +453,8 @@ void SpellcheckCustomDictionary::OnLoaded( fix_invalid_file_.Reset( base::BindOnce(&SpellcheckCustomDictionary::FixInvalidFile, weak_ptr_factory_.GetWeakPtr(), std::move(result))); - base::PostTask( - FROM_HERE, - {content::BrowserThread::UI, base::TaskPriority::BEST_EFFORT}, - fix_invalid_file_.callback()); + content::GetUIThreadTaskRunner({base::TaskPriority::BEST_EFFORT}) + ->PostTask(FROM_HERE, fix_invalid_file_.callback()); } } diff --git a/chromium/chrome/browser/spellchecker/spellcheck_factory.cc b/chromium/chrome/browser/spellchecker/spellcheck_factory.cc index 5253d1f48e1..229f29e47b3 100644 --- a/chromium/chrome/browser/spellchecker/spellcheck_factory.cc +++ b/chromium/chrome/browser/spellchecker/spellcheck_factory.cc @@ -43,13 +43,6 @@ KeyedService* SpellcheckServiceFactory::BuildServiceInstanceFor( // Many variables are initialized from the |context| in the SpellcheckService. SpellcheckService* spellcheck = new SpellcheckService(context); - PrefService* prefs = user_prefs::UserPrefs::Get(context); - DCHECK(prefs); - - // Instantiates Metrics object for spellchecking for use. - spellcheck->StartRecordingMetrics( - prefs->GetBoolean(spellcheck::prefs::kSpellCheckEnable)); - return spellcheck; } diff --git a/chromium/chrome/browser/spellchecker/spellcheck_factory.h b/chromium/chrome/browser/spellchecker/spellcheck_factory.h index 7adf9db4f84..66e05d429d7 100644 --- a/chromium/chrome/browser/spellchecker/spellcheck_factory.h +++ b/chromium/chrome/browser/spellchecker/spellcheck_factory.h @@ -8,6 +8,7 @@ #include "base/gtest_prod_util.h" #include "base/macros.h" #include "base/memory/singleton.h" +#include "build/build_config.h" #include "components/keyed_service/content/browser_context_keyed_service_factory.h" class SpellcheckService; @@ -39,6 +40,12 @@ class SpellcheckServiceFactory : public BrowserContextKeyedServiceFactory { bool ServiceIsNULLWhileTesting() const override; FRIEND_TEST_ALL_PREFIXES(SpellcheckServiceBrowserTest, DeleteCorruptedBDICT); +#if defined(OS_WIN) + FRIEND_TEST_ALL_PREFIXES(SpellcheckServiceWindowsHybridBrowserTest, + WindowsHybridSpellcheck); + FRIEND_TEST_ALL_PREFIXES(SpellcheckServiceWindowsHybridBrowserTestDelayInit, + WindowsHybridSpellcheckDelayInit); +#endif // defined(OS_WIN) DISALLOW_COPY_AND_ASSIGN(SpellcheckServiceFactory); }; diff --git a/chromium/chrome/browser/spellchecker/spellcheck_hunspell_dictionary.cc b/chromium/chrome/browser/spellchecker/spellcheck_hunspell_dictionary.cc index 25feeebf536..5272b99db1d 100644 --- a/chromium/chrome/browser/spellchecker/spellcheck_hunspell_dictionary.cc +++ b/chromium/chrome/browser/spellchecker/spellcheck_hunspell_dictionary.cc @@ -90,48 +90,54 @@ bool SaveDictionaryData(std::unique_ptr<std::string> data, } // namespace -SpellcheckHunspellDictionary::DictionaryFile::DictionaryFile() { -} +SpellcheckHunspellDictionary::DictionaryFile::DictionaryFile( + base::TaskRunner* task_runner) : task_runner_(task_runner) {} SpellcheckHunspellDictionary::DictionaryFile::~DictionaryFile() { + if (file.IsValid()) { + task_runner_->PostTask(FROM_HERE, + base::BindOnce(&CloseDictionary, std::move(file))); + } } SpellcheckHunspellDictionary::DictionaryFile::DictionaryFile( DictionaryFile&& other) - : path(other.path), file(std::move(other.file)) {} + : path(other.path), + file(std::move(other.file)), + task_runner_(std::move(other.task_runner_)) {} SpellcheckHunspellDictionary::DictionaryFile& SpellcheckHunspellDictionary::DictionaryFile::operator=( DictionaryFile&& other) { path = other.path; file = std::move(other.file); + task_runner_ = std::move(other.task_runner_); return *this; } SpellcheckHunspellDictionary::SpellcheckHunspellDictionary( const std::string& language, + const std::string& platform_spellcheck_language, content::BrowserContext* browser_context, SpellcheckService* spellcheck_service) : task_runner_( base::ThreadPool::CreateSequencedTaskRunner({base::MayBlock()})), language_(language), + platform_spellcheck_language_(platform_spellcheck_language), use_browser_spellchecker_(false), browser_context_(browser_context), spellcheck_service_(spellcheck_service), - download_status_(DOWNLOAD_NONE) {} + download_status_(DOWNLOAD_NONE), + dictionary_file_(task_runner_.get()) {} SpellcheckHunspellDictionary::~SpellcheckHunspellDictionary() { - if (dictionary_file_.file.IsValid()) { - task_runner_->PostTask( - FROM_HERE, - base::BindOnce(&CloseDictionary, std::move(dictionary_file_.file))); - } - #if BUILDFLAG(USE_BROWSER_SPELLCHECKER) // Disable the language from platform spellchecker. - if (spellcheck::UseBrowserSpellChecker()) + if (spellcheck::UseBrowserSpellChecker() && HasPlatformSupport()) { spellcheck_platform::DisableLanguage( - spellcheck_service_->platform_spell_checker(), language_); + spellcheck_service_->platform_spell_checker(), + GetPlatformSpellcheckLanguage()); + } #endif // BUILDFLAG(USE_BROWSER_SPELLCHECKER) } @@ -140,9 +146,10 @@ void SpellcheckHunspellDictionary::Load() { #if BUILDFLAG(USE_BROWSER_SPELLCHECKER) if (spellcheck::UseBrowserSpellChecker() && - spellcheck_platform::SpellCheckerAvailable()) { + spellcheck_platform::SpellCheckerAvailable() && HasPlatformSupport()) { spellcheck_platform::PlatformSupportsLanguage( - spellcheck_service_->platform_spell_checker(), language_, + spellcheck_service_->platform_spell_checker(), + GetPlatformSpellcheckLanguage(), base::BindOnce( &SpellcheckHunspellDictionary::PlatformSupportsLanguageComplete, weak_ptr_factory_.GetWeakPtr())); @@ -177,6 +184,21 @@ const std::string& SpellcheckHunspellDictionary::GetLanguage() const { return language_; } +const std::string& SpellcheckHunspellDictionary::GetPlatformSpellcheckLanguage() + const { +#if defined(OS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER) + // Currently the platform spellcheck language is only distinguished for + // Windows. + return platform_spellcheck_language_; +#else + return language_; +#endif // defined(OS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER) +} + +bool SpellcheckHunspellDictionary::HasPlatformSupport() const { + return !GetPlatformSpellcheckLanguage().empty(); +} + bool SpellcheckHunspellDictionary::IsUsingPlatformChecker() const { return use_browser_spellchecker_; } @@ -308,13 +330,14 @@ void SpellcheckHunspellDictionary::DownloadDictionary(GURL url) { base::Unretained(this))); // Attempt downloading the dictionary only once. - browser_context_ = NULL; + browser_context_ = nullptr; } #if !defined(OS_ANDROID) // static SpellcheckHunspellDictionary::DictionaryFile -SpellcheckHunspellDictionary::OpenDictionaryFile(const base::FilePath& path) { +SpellcheckHunspellDictionary::OpenDictionaryFile(base::TaskRunner* task_runner, + const base::FilePath& path) { base::ScopedBlockingCall scoped_blocking_call(FROM_HERE, base::BlockingType::MAY_BLOCK); @@ -325,7 +348,7 @@ SpellcheckHunspellDictionary::OpenDictionaryFile(const base::FilePath& path) { // For systemwide installations on Windows, the default directory may not // have permissions for download. In that case, the alternate directory for // download is chrome::DIR_USER_DATA. - DictionaryFile dictionary; + DictionaryFile dictionary(task_runner); #if defined(OS_WIN) // Check if the dictionary exists in the fallback location. If so, use it @@ -367,7 +390,7 @@ SpellcheckHunspellDictionary::OpenDictionaryFile(const base::FilePath& path) { // static SpellcheckHunspellDictionary::DictionaryFile SpellcheckHunspellDictionary::InitializeDictionaryLocation( - const std::string& language) { + base::TaskRunner* task_runner, const std::string& language) { base::ScopedBlockingCall scoped_blocking_call(FROM_HERE, base::BlockingType::MAY_BLOCK); @@ -382,7 +405,7 @@ SpellcheckHunspellDictionary::InitializeDictionaryLocation( base::FilePath dict_path = spellcheck::GetVersionedFileName(language, dict_dir); - return OpenDictionaryFile(dict_path); + return OpenDictionaryFile(task_runner, dict_path); } void SpellcheckHunspellDictionary::InitializeDictionaryLocationComplete( @@ -396,7 +419,7 @@ void SpellcheckHunspellDictionary::InitializeDictionaryLocationComplete( // TODO(rouslan): Remove this test-only case. if (spellcheck_service_->SignalStatusEvent( SpellcheckService::BDICT_CORRUPTED)) { - browser_context_ = NULL; + browser_context_ = nullptr; } if (browser_context_) { @@ -443,9 +466,10 @@ void SpellcheckHunspellDictionary::PlatformSupportsLanguageComplete( if (platform_supports_language) { #if BUILDFLAG(USE_BROWSER_SPELLCHECKER) - if (spellcheck::UseBrowserSpellChecker()) { + if (spellcheck::UseBrowserSpellChecker() && HasPlatformSupport()) { spellcheck_platform::SetLanguage( - spellcheck_service_->platform_spell_checker(), language_, + spellcheck_service_->platform_spell_checker(), + GetPlatformSpellcheckLanguage(), base::BindOnce(&SpellcheckHunspellDictionary:: SpellCheckPlatformSetLanguageComplete, weak_ptr_factory_.GetWeakPtr())); @@ -454,24 +478,14 @@ void SpellcheckHunspellDictionary::PlatformSupportsLanguageComplete( #endif // BUILDFLAG(USE_BROWSER_SPELLCHECKER) NOTREACHED(); } else { -#if defined(OS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER) - // The platform spellchecker doesn't support this language. Fall back to - // Hunspell, unless the hybrid spellchecker is disabled. - if (spellcheck::UseBrowserSpellChecker() && - !spellcheck::UseWinHybridSpellChecker()) { - // Can't fall back to Hunspell, because the hybrid spellchecker is not - // enabled. We can't spellcheck this language, so there's no further - // processing to do. - return; - } -#endif // defined(OS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER) // Either the platform spellchecker is unavailable / disabled, or it doesn't // support this language. In either case, we must use Hunspell for this // language, unless we are on Android, which doesn't support Hunspell. #if !defined(OS_ANDROID) && BUILDFLAG(USE_RENDERER_SPELLCHECKER) base::PostTaskAndReplyWithResult( task_runner_.get(), FROM_HERE, - base::BindOnce(&InitializeDictionaryLocation, language_), + base::BindOnce(&InitializeDictionaryLocation, + base::RetainedRef(task_runner_.get()), language_), base::BindOnce( &SpellcheckHunspellDictionary::InitializeDictionaryLocationComplete, weak_ptr_factory_.GetWeakPtr())); diff --git a/chromium/chrome/browser/spellchecker/spellcheck_hunspell_dictionary.h b/chromium/chrome/browser/spellchecker/spellcheck_hunspell_dictionary.h index 4af3201238d..a8acd00dc44 100644 --- a/chromium/chrome/browser/spellchecker/spellcheck_hunspell_dictionary.h +++ b/chromium/chrome/browser/spellchecker/spellcheck_hunspell_dictionary.h @@ -53,6 +53,7 @@ class SpellcheckHunspellDictionary }; SpellcheckHunspellDictionary(const std::string& language, + const std::string& platform_spellcheck_language, content::BrowserContext* browser_context, SpellcheckService* spellcheck_service); ~SpellcheckHunspellDictionary() override; @@ -68,6 +69,8 @@ class SpellcheckHunspellDictionary const base::File& GetDictionaryFile() const; const std::string& GetLanguage() const; + const std::string& GetPlatformSpellcheckLanguage() const; + bool HasPlatformSupport() const; bool IsUsingPlatformChecker() const; // Add an observer for Hunspell dictionary events. @@ -97,7 +100,7 @@ class SpellcheckHunspellDictionary // blocking sequence. struct DictionaryFile { public: - DictionaryFile(); + explicit DictionaryFile(base::TaskRunner* task_runner); ~DictionaryFile(); DictionaryFile(DictionaryFile&& other); @@ -110,6 +113,9 @@ class SpellcheckHunspellDictionary base::File file; private: + // Task runner where the file is created. + scoped_refptr<base::TaskRunner> task_runner_; + DISALLOW_COPY_AND_ASSIGN(DictionaryFile); }; @@ -124,11 +130,12 @@ class SpellcheckHunspellDictionary #if !defined(OS_ANDROID) // Figures out the location for the dictionary, verifies its contents, and // opens it. - static DictionaryFile OpenDictionaryFile(const base::FilePath& path); + static DictionaryFile OpenDictionaryFile(base::TaskRunner* task_runner, + const base::FilePath& path); // Gets the default location for the dictionary file. static DictionaryFile InitializeDictionaryLocation( - const std::string& language); + base::TaskRunner* task_runner, const std::string& language); // The reply point for PostTaskAndReplyWithResult, called after the dictionary // file has been initialized. @@ -156,9 +163,14 @@ class SpellcheckHunspellDictionary // Task runner where the file operations takes place. scoped_refptr<base::SequencedTaskRunner> const task_runner_; - // The language of the dictionary file. + // The language of the dictionary file (passed when loading Hunspell + // dictionaries). const std::string language_; + // The spellcheck language passed to platform APIs may differ from the accept + // language (can be empty, indicating to use accept language and Hunspell). + const std::string platform_spellcheck_language_; + // Whether to use the platform spellchecker instead of Hunspell. bool use_browser_spellchecker_; diff --git a/chromium/chrome/browser/spellchecker/spellcheck_language_policy_handlers_unittest.cc b/chromium/chrome/browser/spellchecker/spellcheck_language_policy_handlers_unittest.cc index fd5ff6c2907..4dcf37eeeca 100644 --- a/chromium/chrome/browser/spellchecker/spellcheck_language_policy_handlers_unittest.cc +++ b/chromium/chrome/browser/spellchecker/spellcheck_language_policy_handlers_unittest.cc @@ -17,6 +17,7 @@ #include "base/strings/string_util.h" #include "base/test/scoped_feature_list.h" #include "base/values.h" +#include "build/build_config.h" #include "chrome/common/pref_names.h" #include "components/policy/core/common/policy_map.h" #include "components/policy/policy_constants.h" @@ -102,22 +103,19 @@ class SpellcheckLanguagePolicyHandlersTest }; TEST_P(SpellcheckLanguagePolicyHandlersTest, ApplyPolicySettings) { -#if BUILDFLAG(USE_WIN_HYBRID_SPELLCHECKER) +#if defined(OS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER) base::test::ScopedFeatureList feature_list; if (GetParam().windows_spellchecker_enabled) { if (!spellcheck::WindowsVersionSupportsSpellchecker()) return; - // Force hybrid spellchecking to be enabled. - feature_list.InitWithFeatures( - /*enabled_features=*/{spellcheck::kWinUseBrowserSpellChecker, - spellcheck::kWinUseHybridSpellChecker}, - /*disabled_features=*/{}); + // Force Windows native spellchecking to be enabled. + feature_list.InitAndEnableFeature(spellcheck::kWinUseBrowserSpellChecker); } else { // Hunspell-only spellcheck languages will be used. feature_list.InitAndDisableFeature(spellcheck::kWinUseBrowserSpellChecker); } -#endif // BUILDFLAG(USE_WIN_HYBRID_SPELLCHECKER) +#endif // defined(OS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER) PrefValueMap prefs; policy::PolicyMap policy; @@ -169,7 +167,7 @@ INSTANTIATE_TEST_SUITE_P( TestCases, SpellcheckLanguagePolicyHandlersTest, testing::Values( -#if BUILDFLAG(USE_WIN_HYBRID_SPELLCHECKER) +#if defined(OS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER) // Test cases for Windows spellchecker (policy languages not restricted // to Hunspell). TestCase({"ar-SA", "es-MX", "fi", "fr", @@ -185,7 +183,7 @@ INSTANTIATE_TEST_SUITE_P( {""} /* expected forced languages */, false /* spellcheck enabled */, true /* windows spellchecker enabled */), -#endif // BUILDFLAG(USE_WIN_HYBRID_SPELLCHECKER) +#endif // defined(OS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER) // Test cases for Hunspell only spellchecker. ar-SA and fi are // non-Hunspell languages so are ignored for policy enforcement. If they // ever obtain Hunspell support, the first test case below will fail. diff --git a/chromium/chrome/browser/spellchecker/spellcheck_service.cc b/chromium/chrome/browser/spellchecker/spellcheck_service.cc index af11ea50738..69a1dd21df9 100644 --- a/chromium/chrome/browser/spellchecker/spellcheck_service.cc +++ b/chromium/chrome/browser/spellchecker/spellcheck_service.cc @@ -18,11 +18,13 @@ #include "base/synchronization/waitable_event.h" #include "base/values.h" #include "build/build_config.h" +#include "chrome/browser/first_run/first_run.h" #include "chrome/browser/spellchecker/spellcheck_factory.h" #include "chrome/browser/spellchecker/spellcheck_hunspell_dictionary.h" #include "components/language/core/browser/pref_names.h" #include "components/prefs/pref_member.h" #include "components/prefs/pref_service.h" +#include "components/prefs/scoped_user_pref_update.h" #include "components/spellcheck/browser/pref_names.h" #include "components/spellcheck/browser/spellcheck_host_metrics.h" #include "components/spellcheck/browser/spellcheck_platform.h" @@ -71,15 +73,6 @@ SpellcheckService::SpellcheckService(content::BrowserContext* context) : context_(context) { DCHECK_CURRENTLY_ON(BrowserThread::UI); -#if defined(OS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER) - // The Windows spell checker must be created before the dictionaries are - // initialized. - if (spellcheck::WindowsVersionSupportsSpellchecker()) { - platform_spell_checker_ = std::make_unique<WindowsSpellChecker>( - base::ThreadPool::CreateCOMSTATaskRunner({base::MayBlock()})); - } -#endif // defined(OS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER) - PrefService* prefs = user_prefs::UserPrefs::Get(context); pref_change_registrar_.Init(prefs); StringListPrefMember dictionaries_pref; @@ -114,6 +107,21 @@ SpellcheckService::SpellcheckService(content::BrowserContext* context) single_dictionary_pref.SetValue(""); #endif // defined(OS_MACOSX) || defined(OS_ANDROID) +#if defined(OS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER) + if (!spellcheck::UseBrowserSpellChecker()) { + // A user may have disabled the Windows spellcheck feature after adding + // non-Hunspell supported languages on the language settings page. Remove + // preferences for non-Hunspell languages so that there is no attempt to + // load a non-existent Hunspell dictionary, and so that Hunspell + // spellchecking isn't broken because of the failed load. + ListPrefUpdate update(prefs, spellcheck::prefs::kSpellCheckDictionaries); + update->EraseListValueIf([](const base::Value& entry) { + return spellcheck::GetCorrespondingSpellCheckLanguage(entry.GetString()) + .empty(); + }); + } +#endif // defined(OS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER) + pref_change_registrar_.Add( spellcheck::prefs::kSpellCheckDictionaries, base::BindRepeating(&SpellcheckService::OnSpellCheckDictionariesChanged, @@ -143,11 +151,21 @@ SpellcheckService::SpellcheckService(content::BrowserContext* context) custom_dictionary_->AddObserver(this); custom_dictionary_->Load(); - registrar_.Add(this, - content::NOTIFICATION_RENDERER_PROCESS_CREATED, + registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_CREATED, content::NotificationService::AllSources()); - LoadHunspellDictionaries(); +#if defined(OS_WIN) + if (spellcheck::UseBrowserSpellChecker() && + base::FeatureList::IsEnabled( + spellcheck::kWinDelaySpellcheckServiceInit)) { + // If initialization of the spellcheck service is on-demand, it is up to the + // instantiator of the spellcheck service to call InitializeDictionaries + // with a callback. + return; + } +#endif // defined(OS_WIN) + + InitializeDictionaries(base::DoNothing()); } SpellcheckService::~SpellcheckService() { @@ -161,8 +179,9 @@ base::WeakPtr<SpellcheckService> SpellcheckService::GetWeakPtr() { #if !defined(OS_MACOSX) // static -void SpellcheckService::GetDictionaries(base::SupportsUserData* browser_context, - std::vector<Dictionary>* dictionaries) { +void SpellcheckService::GetDictionaries( + content::BrowserContext* browser_context, + std::vector<Dictionary>* dictionaries) { PrefService* prefs = user_prefs::UserPrefs::Get(browser_context); std::set<std::string> spellcheck_dictionaries; for (const auto& value : @@ -178,8 +197,23 @@ void SpellcheckService::GetDictionaries(base::SupportsUserData* browser_context, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); for (const auto& accept_language : accept_languages) { Dictionary dictionary; +#if defined(OS_WIN) + if (spellcheck::UseBrowserSpellChecker()) { + SpellcheckService* spellcheck = + SpellcheckServiceFactory::GetForContext(browser_context); + if (spellcheck && spellcheck->UsesWindowsDictionary(accept_language)) + dictionary.language = accept_language; + } + + if (dictionary.language.empty()) { + dictionary.language = + spellcheck::GetCorrespondingSpellCheckLanguage(accept_language); + } +#else dictionary.language = spellcheck::GetCorrespondingSpellCheckLanguage(accept_language); +#endif // defined(OS_WIN) + if (dictionary.language.empty()) continue; @@ -212,9 +246,13 @@ std::string SpellcheckService::GetSupportedAcceptLanguageCode( supported_language_full_tag); #if defined(OS_WIN) - if (!spellcheck::UseWinHybridSpellChecker()) + if (!spellcheck::UseBrowserSpellChecker()) return supported_accept_language; + // Exclude dictionaries that are for private use, such as "ja-Latn-JP-x-ext". + if (SpellcheckService::HasPrivateUseSubTag(supported_language_full_tag)) + return ""; + // Collect the hardcoded list of accept-languages supported by the browser, // that is, languages that can be added as preferred languages in the // languages settings page. @@ -272,8 +310,18 @@ std::string SpellcheckService::GetSupportedAcceptLanguageCode( /* include_script_tag */ false)); }); - if (iter != accept_languages.end()) + if (iter != accept_languages.end()) { + // Special case for Serbian--"sr" implies Cyrillic script. Don't mark it as + // supported for sr-Latn*. + if (base::EqualsCaseInsensitiveASCII( + SpellcheckService::GetLanguageAndScriptTag( + supported_language_full_tag, + /* include_script_tag */ true), + "sr-Latn")) { + return ""; + } return *iter; + } #endif // defined(OS_WIN) @@ -328,7 +376,7 @@ SpellcheckCustomDictionary* SpellcheckService::GetCustomDictionary() { return custom_dictionary_.get(); } -void SpellcheckService::LoadHunspellDictionaries() { +void SpellcheckService::LoadDictionaries() { hunspell_dictionaries_.clear(); PrefService* prefs = user_prefs::UserPrefs::Get(context_); @@ -359,16 +407,50 @@ void SpellcheckService::LoadHunspellDictionaries() { } for (const auto& dictionary : dictionaries) { + // The spellcheck language passed to platform APIs may differ from the + // accept language. + std::string platform_spellcheck_language; +#if defined(OS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER) + if (spellcheck::UseBrowserSpellChecker()) { + std::string windows_dictionary_name = + GetSupportedWindowsDictionaryLanguage(dictionary); + if (!windows_dictionary_name.empty()) { + platform_spellcheck_language = + SpellcheckService::GetTagToPassToWindowsSpellchecker( + dictionary, windows_dictionary_name); + } + } +#endif // defined(OS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER) + hunspell_dictionaries_.push_back( - std::make_unique<SpellcheckHunspellDictionary>(dictionary, context_, - this)); + std::make_unique<SpellcheckHunspellDictionary>( + dictionary, platform_spellcheck_language, context_, this)); hunspell_dictionaries_.back()->AddObserver(this); hunspell_dictionaries_.back()->Load(); } #if defined(OS_WIN) RecordSpellcheckLocalesStats(); + +#if BUILDFLAG(USE_BROWSER_SPELLCHECKER) + if (base::FeatureList::IsEnabled( + spellcheck::kWinDelaySpellcheckServiceInit) && + spellcheck::UseBrowserSpellChecker()) { + // Only want to fire the callback on first call to LoadDictionaries + // originating from InitializeDictionaries, since supported platform + // dictionaries are cached throughout the browser session and not + // dynamically updated. LoadDictionaries can be called multiple times in a + // browser session, even before InitializeDictionaries is called, e.g. when + // language settings are changed. + if (!dictionaries_loaded() && dictionaries_loaded_callback_) { + dictionaries_loaded_ = true; + std::move(dictionaries_loaded_callback_).Run(); + } + return; + } +#endif // BUILDFLAG(USE_BROWSER_SPELLCHECKER) #endif // defined(OS_WIN) + dictionaries_loaded_ = true; } const std::vector<std::unique_ptr<SpellcheckHunspellDictionary>>& @@ -435,6 +517,134 @@ void SpellcheckService::OnHunspellDictionaryDownloadFailure( const std::string& language) { } +void SpellcheckService::InitializeDictionaries(base::OnceClosure done) { + // The dictionaries only need to be initialized once. + if (dictionaries_loaded()) { + std::move(done).Run(); + return; + } + +#if defined(OS_WIN) + dictionaries_loaded_callback_ = std::move(done); + // Need to initialize the platform spellchecker in order to record platform + // locale stats even if the platform spellcheck feature is disabled. + InitializePlatformSpellchecker(); +#endif // defined(OS_WIN) + + PrefService* prefs = user_prefs::UserPrefs::Get(context_); + DCHECK(prefs); + + // Instantiates Metrics object for spellchecking to use. + StartRecordingMetrics( + prefs->GetBoolean(spellcheck::prefs::kSpellCheckEnable)); + +#if defined(OS_WIN) + if (spellcheck::UseBrowserSpellChecker() && platform_spell_checker()) { + spellcheck_platform::RetrieveSpellcheckLanguages( + platform_spell_checker(), + base::BindOnce(&SpellcheckService::InitWindowsDictionaryLanguages, + GetWeakPtr())); + return; + } +#endif // defined(OS_WIN) + + // Using Hunspell. + LoadDictionaries(); +} + +#if defined(OS_WIN) +void SpellcheckService::InitWindowsDictionaryLanguages( + const std::vector<std::string>& windows_spellcheck_languages) { + windows_spellcheck_dictionary_map_.clear(); + for (const auto& windows_spellcheck_language : windows_spellcheck_languages) { + std::string accept_language = + SpellcheckService::GetSupportedAcceptLanguageCode( + windows_spellcheck_language); + AddWindowsSpellcheckDictionary(accept_language, + windows_spellcheck_language); + + // There is one unfortunate special case (so far the only one known). The + // accept language "sr" is supported, and if you use it as a display + // language you see Cyrillic script. If a Windows language pack is + // installed that supports "sr-Cyrl-*", mark the "sr" accept language + // as having Windows spellcheck support instead of using Hunspell. + if (base::EqualsCaseInsensitiveASCII( + "sr-Cyrl", SpellcheckService::GetLanguageAndScriptTag( + windows_spellcheck_language, + /* include_script_tag */ true))) { + AddWindowsSpellcheckDictionary("sr", windows_spellcheck_language); + } + } + + // A user may have removed a language pack for a non-Hunspell language after + // enabling it for spellcheck on the language settings page. Remove + // preferences for this language so that there is no attempt to load a + // non-existent Hunspell dictionary, and so that Hunspell spellchecking isn't + // broken because of the failed load. + PrefService* prefs = user_prefs::UserPrefs::Get(context_); + DCHECK(prefs); + // When following object goes out of scope, preference change observers will + // be notified (even if there is no preference change). + ListPrefUpdate update(prefs, spellcheck::prefs::kSpellCheckDictionaries); + update->EraseListValueIf([this](const base::Value& entry) { + const std::string dictionary_name = entry.GetString(); + return (!UsesWindowsDictionary(dictionary_name) && + spellcheck::GetCorrespondingSpellCheckLanguage(dictionary_name) + .empty()); + }); + + if (first_run::IsChromeFirstRun()) { + // Ensure that spellcheck is enabled for the first dialect of the + // accepted languages if there is a Windows dictionary installed for + // that dialect. + base::Value user_dictionaries = + prefs->GetList(spellcheck::prefs::kSpellCheckDictionaries)->Clone(); + std::vector<std::string> user_languages = + base::SplitString(prefs->GetString(language::prefs::kAcceptLanguages), + ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); + + // Some first run scenarios will add an accept language to preferences that + // is not found in the hard-coded list in kAcceptLanguageList. Only + // languages in kAcceptLanguageList can be spellchecked. An example is an + // installation on a device where Finnnish is the Windows display + // language--the initial accept language preferences are observed to be + // "fi-FI,fi,en-US,en". Only "fi" is contained in kAcceptLanguageList. + std::string first_user_language; + std::vector<std::string> accept_languages; + l10n_util::GetAcceptLanguages(&accept_languages); + for (const auto& user_language : user_languages) { + if (base::Contains(accept_languages, user_language)) { + first_user_language = user_language; + break; + } + } + + bool first_user_language_spellchecked = false; + for (const auto& dictionary_value : user_dictionaries.GetList()) { + first_user_language_spellchecked = + base::Contains(dictionary_value.GetString(), first_user_language); + if (first_user_language_spellchecked) + break; + } + + if (!first_user_language_spellchecked && + UsesWindowsDictionary(first_user_language)) { + user_dictionaries.Insert(user_dictionaries.GetList().begin(), + base::Value(first_user_language)); + prefs->Set(spellcheck::prefs::kSpellCheckDictionaries, user_dictionaries); + } + } + + // No need to call LoadDictionaries() as when the ListPrefUpdate + // object goes out of scope, the preference change handler will do this. +} + +bool SpellcheckService::UsesWindowsDictionary( + std::string accept_language) const { + return !GetSupportedWindowsDictionaryLanguage(accept_language).empty(); +} +#endif // defined(OS_WIN) + // static void SpellcheckService::OverrideBinderForTesting(SpellCheckerBinder binder) { GetSpellCheckerBinderOverride() = std::move(binder); @@ -468,6 +678,50 @@ std::string SpellcheckService::GetLanguageAndScriptTag( } // static +bool SpellcheckService::HasPrivateUseSubTag(const std::string& full_tag) { + std::vector<std::string> subtags = base::SplitString( + full_tag, "-", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); + + // Private use subtags are separated from the other subtags by the reserved + // single-character subtag 'x'. + return base::Contains(subtags, "x"); +} + +#if defined(OS_WIN) +// static +std::string SpellcheckService::GetTagToPassToWindowsSpellchecker( + const std::string& accept_language, + const std::string& supported_language_full_tag) { + // First try exact match. Per BCP47, tags are in ASCII and should be treated + // as case-insensitive (although there are conventions for the capitalization + // of subtags, they are sometimes broken). + if (base::EqualsCaseInsensitiveASCII(supported_language_full_tag, + accept_language)) { + // Unambiguous spellcheck dictionary to be used. + return supported_language_full_tag; + } + + // Accept language does not match script or region subtags. + // If there is a script subtag, include it, to avoid for example passing + // "sr" which is ambiguous as Serbian can use Cyrillic or Latin script. + // There is one unfortunate special case (so far the only one known). The + // accept language "sr" is supported, and if you use it as a display + // language you see Cyrillic script. However, the Windows spellcheck API + // returns "sr-Latn-*" dictionaries if the unqualified language tag is + // passed. The following forces Windows spellchecking to use Cyrillic script + // in this case, and if the language pack is not installed there will be a + // fallback to Hunspell support when spellchecking is performed. + if (base::EqualsCaseInsensitiveASCII("sr", accept_language)) + return "sr-Cyrl"; + + return SpellcheckService::GetLanguageAndScriptTag( + supported_language_full_tag, + /* include_script_tag */ true); +} + +#endif // defined(OS_WIN) + +// static void SpellcheckService::AttachStatusEvent(base::WaitableEvent* status_event) { DCHECK_CURRENTLY_ON(BrowserThread::UI); @@ -495,7 +749,7 @@ SpellcheckService::GetSpellCheckerForProcess(content::RenderProcessHost* host) { void SpellcheckService::InitForAllRenderers() { DCHECK_CURRENTLY_ON(BrowserThread::UI); for (content::RenderProcessHost::iterator i( - content::RenderProcessHost::AllHostsIterator()); + content::RenderProcessHost::AllHostsIterator()); !i.IsAtEnd(); i.Advance()) { content::RenderProcessHost* process = i.GetCurrentValue(); if (process && process->GetProcess().Handle()) @@ -506,7 +760,7 @@ void SpellcheckService::InitForAllRenderers() { void SpellcheckService::OnSpellCheckDictionariesChanged() { // If there are hunspell dictionaries, then fire off notifications to the // renderers after the dictionaries are finished loading. - LoadHunspellDictionaries(); + LoadDictionaries(); // If there are no hunspell dictionaries to load, then immediately let the // renderers know the new state. @@ -551,15 +805,35 @@ std::vector<std::string> SpellcheckService::GetNormalizedAcceptLanguages( ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); if (normalize_for_spellcheck) { - std::transform(accept_languages.begin(), accept_languages.end(), - accept_languages.begin(), - &spellcheck::GetCorrespondingSpellCheckLanguage); + std::transform( + accept_languages.begin(), accept_languages.end(), + accept_languages.begin(), [&](const std::string& language) { +#if defined(OS_WIN) + if (spellcheck::UseBrowserSpellChecker() && + UsesWindowsDictionary(language)) + return language; +#endif // defined(OS_WIN) + return spellcheck::GetCorrespondingSpellCheckLanguage(language); + }); } return accept_languages; } #if defined(OS_WIN) +void SpellcheckService::InitializePlatformSpellchecker() { + // The Windows spell checker must be created before the dictionaries are + // initialized. Note it is instantiated even if only Hunspell is being used + // since metrics on the availability of Windows platform language packs are + // being recorded. Thus method should only be called once, except in test + // code. + if (!platform_spell_checker() && + spellcheck::WindowsVersionSupportsSpellchecker()) { + platform_spell_checker_ = std::make_unique<WindowsSpellChecker>( + base::ThreadPool::CreateCOMSTATaskRunner({base::MayBlock()})); + } +} + void SpellcheckService::RecordSpellcheckLocalesStats() { if (spellcheck::WindowsVersionSupportsSpellchecker() && metrics_ && platform_spell_checker() && !hunspell_dictionaries_.empty()) { @@ -581,4 +855,34 @@ void SpellcheckService::RecordChromeLocalesStats() { platform_spell_checker(), std::move(accept_languages), metrics_.get()); } } + +void SpellcheckService::AddWindowsSpellcheckDictionary( + const std::string& accept_language, + const std::string& supported_language_full_tag) { + if (!accept_language.empty()) { + windows_spellcheck_dictionary_map_.insert( + {accept_language, supported_language_full_tag}); + } +} + +std::string SpellcheckService::GetSupportedWindowsDictionaryLanguage( + const std::string& accept_language) const { + // BCP47 language tag used by the Windows spellchecker API. + std::string spellcheck_language; + + auto it = windows_spellcheck_dictionary_map_.find(accept_language); + if (it != windows_spellcheck_dictionary_map_.end()) + spellcheck_language = it->second; + + return spellcheck_language; +} + +void SpellcheckService::AddSpellcheckLanguagesForTesting( + const std::vector<std::string>& languages) { + InitializePlatformSpellchecker(); + if (platform_spell_checker()) { + spellcheck_platform::AddSpellcheckLanguagesForTesting( + platform_spell_checker(), languages); + } +} #endif // defined(OS_WIN) diff --git a/chromium/chrome/browser/spellchecker/spellcheck_service.h b/chromium/chrome/browser/spellchecker/spellcheck_service.h index 97df131375c..4c3d45d9ce6 100644 --- a/chromium/chrome/browser/spellchecker/spellcheck_service.h +++ b/chromium/chrome/browser/spellchecker/spellcheck_service.h @@ -7,6 +7,7 @@ #include <stddef.h> +#include <map> #include <memory> #include <string> #include <vector> @@ -31,7 +32,6 @@ class SpellCheckHostMetrics; namespace base { class WaitableEvent; -class SupportsUserData; } namespace content { @@ -41,6 +41,12 @@ class NotificationSource; class RenderProcessHost; } +#if defined(OS_WIN) +namespace extensions { +class LanguageSettingsPrivateApiTestDelayInit; +} +#endif // defined(OS_WIN) + // Encapsulates the browser side spellcheck service. There is one of these per // profile and each is created by the SpellCheckServiceFactory. The // SpellcheckService maintains any per-profile information about spellcheck. @@ -84,7 +90,7 @@ class SpellcheckService : public KeyedService, // Returns all currently configured |dictionaries| to display in the context // menu over a text area. The context menu is used for selecting the // dictionaries used for spellcheck. - static void GetDictionaries(base::SupportsUserData* browser_context, + static void GetDictionaries(content::BrowserContext* browser_context, std::vector<Dictionary>* dictionaries); #endif // !OS_MACOSX @@ -116,9 +122,6 @@ class SpellcheckService : public KeyedService, // Returns the instance of the custom dictionary. SpellcheckCustomDictionary* GetCustomDictionary(); - // Starts the process of loading the Hunspell dictionaries. - void LoadHunspellDictionaries(); - // Returns the instance of the vector of Hunspell dictionaries. const std::vector<std::unique_ptr<SpellcheckHunspellDictionary>>& GetHunspellDictionaries(); @@ -152,6 +155,21 @@ class SpellcheckService : public KeyedService, void OnHunspellDictionaryDownloadFailure( const std::string& language) override; + // One-time initialization of dictionaries if needed. + void InitializeDictionaries(base::OnceClosure done); + +#if defined(OS_WIN) + // Callback for spellcheck_platform::RetrieveSpellcheckLanguages. Populates + // map of preferred languages to available platform dictionaries then + // loads the dictionaries. + void InitWindowsDictionaryLanguages( + const std::vector<std::string>& windows_spellcheck_languages); + + // Indicates whether given accept language has Windows spellcheck platform + // support. + bool UsesWindowsDictionary(std::string accept_language) const; +#endif // defined(OS_WIN) + // The returned pointer can be null if the current platform doesn't need a // per-profile, platform-specific spell check object. Currently, only Windows // requires one, and only on certain versions. @@ -159,6 +177,9 @@ class SpellcheckService : public KeyedService, return platform_spell_checker_.get(); } + // Indicates whether dictionaries have been loaded initially. + bool dictionaries_loaded() const { return dictionaries_loaded_; } + // Allows tests to override how SpellcheckService binds its interface // receiver, instead of going through a RenderProcessHost by default. using SpellCheckerBinder = base::RepeatingCallback<void( @@ -167,12 +188,40 @@ class SpellcheckService : public KeyedService, private: FRIEND_TEST_ALL_PREFIXES(SpellcheckServiceBrowserTest, DeleteCorruptedBDICT); +#if defined(OS_WIN) + FRIEND_TEST_ALL_PREFIXES(SpellcheckServiceWindowsHybridBrowserTest, + WindowsHybridSpellcheck); + FRIEND_TEST_ALL_PREFIXES(SpellcheckServiceWindowsHybridBrowserTestDelayInit, + WindowsHybridSpellcheckDelayInit); + friend class SpellcheckServiceHybridUnitTestBase; + friend class SpellcheckServiceHybridUnitTestDelayInitBase; + friend class extensions::LanguageSettingsPrivateApiTestDelayInit; +#endif // defined(OS_WIN) + + // Starts the process of loading the dictionaries (Hunspell and platform). Can + // be called multiple times in a browser session if spellcheck settings + // change. + void LoadDictionaries(); // Parses a full BCP47 language tag to return just the language subtag, // optionally with a hyphen and script subtag appended. static std::string GetLanguageAndScriptTag(const std::string& full_tag, bool include_script_tag); + // Returns true if full BCP47 language tag contains private use subtag (e.g in + // the tag "ja-Latn-JP-x-ext"), indicating the tag is only for use by private + // agreement. + static bool HasPrivateUseSubTag(const std::string& full_tag); + +#if defined(OS_WIN) + // Returns the BCP47 language tag to pass to the Windows spellcheck API, based + // on the accept language and full tag, with special logic for languages that + // can be written in different scripts. + static std::string GetTagToPassToWindowsSpellchecker( + const std::string& accept_language, + const std::string& supported_language_full_tag); +#endif // defined(OS_WIN) + // Attaches an event so browser tests can listen the status events. static void AttachStatusEvent(base::WaitableEvent* status_event); @@ -207,12 +256,36 @@ class SpellcheckService : public KeyedService, bool normalize_for_spellcheck = true) const; #if defined(OS_WIN) + // Initializes the platform spell checker. + void InitializePlatformSpellchecker(); + // Records statistics about spell check support for the user's Chrome locales. void RecordChromeLocalesStats(); // Records statistics about which spell checker supports which of the user's // enabled spell check locales. void RecordSpellcheckLocalesStats(); + + // Adds an item to the cached collection mapping an accept language from + // language settings to a BCP47 language tag to be passed to the Windows + // spellchecker API, guarding against duplicate entries for the same accept + // language. + void AddWindowsSpellcheckDictionary( + const std::string& accept_language, + const std::string& supported_language_full_tag); + + // Gets the BCP47 language tag to pass to Windows spellcheck API, by + // searching through the collection of languages already known to have + // Windows spellchecker support on the system. Can return an empty string + // if there is no Windows spellchecker support for this language on the + // system. + std::string GetSupportedWindowsDictionaryLanguage( + const std::string& accept_language) const; + + // Test-only method for adding fake list of platform spellcheck languages + // before calling InitializeDictionaries(). + void AddSpellcheckLanguagesForTesting( + const std::vector<std::string>& languages); #endif // defined(OS_WIN) // WindowsSpellChecker must be created before the dictionary instantiation and @@ -232,6 +305,21 @@ class SpellcheckService : public KeyedService, std::vector<std::unique_ptr<SpellcheckHunspellDictionary>> hunspell_dictionaries_; +#if defined(OS_WIN) + // Maps accept language tags to Windows spellcheck BCP47 tags, an analog + // of the hardcoded kSupportedSpellCheckerLanguages used for Hunspell, + // with the difference that only language packs installed on the system + // with spellchecker support are included. + std::map<std::string, std::string> windows_spellcheck_dictionary_map_; + + // Callback passed as argument to InitializeDictionaries, and invoked when + // the dictionaries are loaded for the first time. + base::OnceClosure dictionaries_loaded_callback_; +#endif // defined(OS_WIN) + + // Flag indicating dictionaries have been loaded initially. + bool dictionaries_loaded_ = false; + base::WeakPtrFactory<SpellcheckService> weak_ptr_factory_{this}; DISALLOW_COPY_AND_ASSIGN(SpellcheckService); diff --git a/chromium/chrome/browser/spellchecker/spellcheck_service_browsertest.cc b/chromium/chrome/browser/spellchecker/spellcheck_service_browsertest.cc index ef602ba4d53..bf59a235f1b 100644 --- a/chromium/chrome/browser/spellchecker/spellcheck_service_browsertest.cc +++ b/chromium/chrome/browser/spellchecker/spellcheck_service_browsertest.cc @@ -4,6 +4,7 @@ #include "chrome/browser/spellchecker/spellcheck_service.h" +#include <map> #include <string> #include <vector> @@ -18,6 +19,7 @@ #include "base/strings/utf_string_conversions.h" #include "base/synchronization/waitable_event.h" #include "base/test/metrics/histogram_tester.h" +#include "base/test/scoped_feature_list.h" #include "base/threading/thread_restrictions.h" #include "base/values.h" #include "build/build_config.h" @@ -33,6 +35,7 @@ #include "components/spellcheck/browser/pref_names.h" #include "components/spellcheck/common/spellcheck.mojom.h" #include "components/spellcheck/common/spellcheck_common.h" +#include "components/spellcheck/common/spellcheck_features.h" #include "components/spellcheck/common/spellcheck_result.h" #include "components/spellcheck/spellcheck_buildflags.h" #include "components/user_prefs/user_prefs.h" @@ -44,9 +47,9 @@ #include "mojo/public/cpp/bindings/receiver.h" #include "mojo/public/cpp/bindings/remote.h" -#if BUILDFLAG(USE_WIN_HYBRID_SPELLCHECKER) +#if defined(OS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER) #include "components/spellcheck/common/spellcheck_features.h" -#endif // BUILDFLAG(USE_WIN_HYBRID_SPELLCHECKER) +#endif // defined(OS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER) using content::BrowserContext; using content::RenderProcessHost; @@ -56,6 +59,19 @@ class SpellcheckServiceBrowserTest : public InProcessBrowserTest, public: SpellcheckServiceBrowserTest() = default; +#if defined(OS_WIN) + void SetUp() override { + // Tests were designed assuming Hunspell dictionary used and many fail when + // Windows spellcheck is enabled by default. The feature flag needs to be + // disabled in SetUp() instead of the constructor because the derived class + // SpellcheckServiceWindowsHybridBrowserTest overrides the base class + // behavior and sets the spellcheck::kWinUseBrowserSpellChecker feature + // flag. You can't use ScopedFeatureList to initialize a feature flag twice. + feature_list_.InitAndDisableFeature(spellcheck::kWinUseBrowserSpellChecker); + InProcessBrowserTest::SetUp(); + } +#endif // defined(OS_WIN) + void SetUpOnMainThread() override { renderer_.reset(new content::MockRenderProcessHost(GetContext())); renderer_->Init(); @@ -104,12 +120,12 @@ class SpellcheckServiceBrowserTest : public InProcessBrowserTest, SpellcheckService* spellcheck = SpellcheckServiceFactory::GetForContext(renderer_->GetBrowserContext()); -#if BUILDFLAG(USE_WIN_HYBRID_SPELLCHECKER) - if (spellcheck::UseWinHybridSpellChecker()) { - // If the Windows hybrid spell checker is in use, initialization is async. +#if defined(OS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER) + if (spellcheck::UseBrowserSpellChecker()) { + // If the Windows native spell checker is in use, initialization is async. RunTestRunLoop(); } -#endif // BUILDFLAG(USE_WIN_HYBRID_SPELLCHECKER) +#endif // defined(OS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER) ASSERT_NE(nullptr, spellcheck); } @@ -224,6 +240,10 @@ class SpellcheckServiceBrowserTest : public InProcessBrowserTest, // Quits the RunLoop on Mojo request flow completion. base::OnceClosure quit_; +#if defined(OS_WIN) + base::test::ScopedFeatureList feature_list_; +#endif // defined(OS_WIN) + private: // Mocked RenderProcessHost. std::unique_ptr<content::MockRenderProcessHost> renderer_; @@ -461,14 +481,14 @@ IN_PROC_BROWSER_TEST_F(SpellcheckServiceHostBrowserTest, CallSpellingService) { // Tests that we can delete a corrupted BDICT file used by hunspell. We do not // run this test on Mac because Mac does not use hunspell by default. IN_PROC_BROWSER_TEST_F(SpellcheckServiceBrowserTest, DeleteCorruptedBDICT) { -#if BUILDFLAG(USE_WIN_HYBRID_SPELLCHECKER) - if (spellcheck::UseWinHybridSpellChecker()) { - // If doing hybrid spell checking on Windows, Hunspell dictionaries are not +#if defined(OS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER) + if (spellcheck::UseBrowserSpellChecker()) { + // If doing native spell checking on Windows, Hunspell dictionaries are not // used for en-US, so the corrupt dictionary event will never be raised. // Skip this test. return; } -#endif // BUILDFLAG(USE_WIN_HYBRID_SPELLCHECKER) +#endif // defined(OS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER) // Corrupted BDICT data: please do not use this BDICT data for other tests. const uint8_t kCorruptedBDICT[] = { @@ -509,13 +529,15 @@ IN_PROC_BROWSER_TEST_F(SpellcheckServiceBrowserTest, DeleteCorruptedBDICT) { SpellcheckServiceFactory::GetInstance()->GetServiceForBrowserContext( context, false)); - ASSERT_EQ(NULL, service); + ASSERT_EQ(nullptr, service); // Getting the spellcheck_service will initialize the SpellcheckService // object with the corrupted BDICT file created above since the hunspell // dictionary is loaded in the SpellcheckService constructor right now. // The SpellCheckHost object will send a BDICT_CORRUPTED event. - SpellcheckServiceFactory::GetForContext(context); + service = SpellcheckServiceFactory::GetForContext(context); + ASSERT_NE(nullptr, service); + ASSERT_TRUE(service->dictionaries_loaded()); // Check the received event. Also we check if Chrome has successfully deleted // the corrupted dictionary. We delete the corrupted dictionary to avoid @@ -613,3 +635,132 @@ IN_PROC_BROWSER_TEST_F(SpellcheckServiceBrowserTest, ->GetString(1, &pref)); EXPECT_EQ("fr", pref); } + +#if defined(OS_WIN) +class SpellcheckServiceWindowsHybridBrowserTest + : public SpellcheckServiceBrowserTest { + public: + SpellcheckServiceWindowsHybridBrowserTest() = default; + + void SetUp() override { + feature_list_.InitAndEnableFeature(spellcheck::kWinUseBrowserSpellChecker); + InProcessBrowserTest::SetUp(); + } +}; + +IN_PROC_BROWSER_TEST_F(SpellcheckServiceWindowsHybridBrowserTest, + WindowsHybridSpellcheck) { + if (!spellcheck::WindowsVersionSupportsSpellchecker()) + return; + + ASSERT_TRUE(spellcheck::UseBrowserSpellChecker()); + + // Note that the base class forces dictionary sync to not be performed, which + // on its own would have created a SpellcheckService object. So testing here + // that we are still instantiating the SpellcheckService as a browser startup + // task to support hybrid spellchecking. + SpellcheckService* service = static_cast<SpellcheckService*>( + SpellcheckServiceFactory::GetInstance()->GetServiceForBrowserContext( + GetContext(), /* create */ false)); + ASSERT_NE(nullptr, service); + + // The list of Windows spellcheck languages should have been populated by at + // least one language. This assures that the spellcheck context menu will + // include Windows spellcheck languages that lack Hunspell support. + ASSERT_TRUE(service->dictionaries_loaded()); + ASSERT_FALSE(service->windows_spellcheck_dictionary_map_.empty()); +} + +class SpellcheckServiceWindowsHybridBrowserTestDelayInit + : public SpellcheckServiceBrowserTest { + public: + SpellcheckServiceWindowsHybridBrowserTestDelayInit() = default; + + void SetUp() override { + // Don't initialize the SpellcheckService on browser launch. + feature_list_.InitWithFeatures( + /*enabled_features=*/{spellcheck::kWinUseBrowserSpellChecker, + spellcheck::kWinDelaySpellcheckServiceInit}, + /*disabled_features=*/{}); + InProcessBrowserTest::SetUp(); + } + + void OnDictionariesInitialized() { + dictionaries_initialized_received_ = true; + if (quit_on_callback_) + std::move(quit_on_callback_).Run(); + } + + protected: + void RunUntilCallbackReceived() { + if (dictionaries_initialized_received_) + return; + base::RunLoop run_loop; + quit_on_callback_ = run_loop.QuitClosure(); + run_loop.Run(); + + // reset status. + dictionaries_initialized_received_ = false; + } + + private: + bool dictionaries_initialized_received_ = false; + + // Quits the RunLoop on receiving the callback from InitializeDictionaries. + base::OnceClosure quit_on_callback_; +}; + +IN_PROC_BROWSER_TEST_F(SpellcheckServiceWindowsHybridBrowserTestDelayInit, + WindowsHybridSpellcheckDelayInit) { + if (!spellcheck::WindowsVersionSupportsSpellchecker()) + return; + + ASSERT_TRUE(spellcheck::UseBrowserSpellChecker()); + + // Note that the base class forces dictionary sync to not be performed, and + // the kWinDelaySpellcheckServiceInit flag is set, which together should + // prevent creation of a SpellcheckService object on browser startup. So + // testing here that this is indeed the case. + SpellcheckService* service = static_cast<SpellcheckService*>( + SpellcheckServiceFactory::GetInstance()->GetServiceForBrowserContext( + GetContext(), /* create */ false)); + ASSERT_EQ(nullptr, service); + + // Now create the SpellcheckService but don't call InitializeDictionaries(). + service = static_cast<SpellcheckService*>( + SpellcheckServiceFactory::GetInstance()->GetServiceForBrowserContext( + GetContext(), /* create */ true)); + + ASSERT_NE(nullptr, service); + + // The list of Windows spellcheck languages should not have been populated + // yet since InitializeDictionaries() has not been called. + ASSERT_FALSE(service->dictionaries_loaded()); + ASSERT_TRUE(service->windows_spellcheck_dictionary_map_.empty()); + + service->InitializeDictionaries( + base::BindOnce(&SpellcheckServiceWindowsHybridBrowserTestDelayInit:: + OnDictionariesInitialized, + base::Unretained(this))); + + RunUntilCallbackReceived(); + ASSERT_TRUE(service->dictionaries_loaded()); + // The list of Windows spellcheck languages should now have been populated. + std::map<std::string, std::string> + windows_spellcheck_dictionary_map_first_call = + service->windows_spellcheck_dictionary_map_; + ASSERT_FALSE(windows_spellcheck_dictionary_map_first_call.empty()); + + // It should be safe to call InitializeDictionaries again (it should + // immediately run the callback). + service->InitializeDictionaries( + base::BindOnce(&SpellcheckServiceWindowsHybridBrowserTestDelayInit:: + OnDictionariesInitialized, + base::Unretained(this))); + + RunUntilCallbackReceived(); + ASSERT_TRUE(service->dictionaries_loaded()); + ASSERT_EQ(windows_spellcheck_dictionary_map_first_call, + service->windows_spellcheck_dictionary_map_); +} +#endif // defined(OS_WIN) diff --git a/chromium/chrome/browser/spellchecker/spellcheck_service_unittest.cc b/chromium/chrome/browser/spellchecker/spellcheck_service_unittest.cc index 3a4fa901884..c4ecd5f7f14 100644 --- a/chromium/chrome/browser/spellchecker/spellcheck_service_unittest.cc +++ b/chromium/chrome/browser/spellchecker/spellcheck_service_unittest.cc @@ -12,41 +12,38 @@ #include "base/strings/string_split.h" #include "base/strings/string_util.h" #include "base/supports_user_data.h" +#include "base/test/scoped_feature_list.h" +#include "build/build_config.h" +#include "chrome/browser/first_run/first_run.h" +#include "chrome/browser/spellchecker/spellcheck_factory.h" #include "chrome/common/chrome_switches.h" #include "chrome/common/pref_names.h" +#include "chrome/test/base/testing_profile.h" #include "components/language/core/browser/pref_names.h" -#include "components/prefs/pref_registry_simple.h" #include "components/prefs/testing_pref_service.h" #include "components/spellcheck/browser/pref_names.h" +#include "components/spellcheck/browser/spellcheck_platform.h" +#include "components/spellcheck/common/spellcheck_features.h" #include "components/user_prefs/user_prefs.h" #include "content/public/test/browser_task_environment.h" #include "testing/gtest/include/gtest/gtest.h" struct TestCase { - TestCase(const std::string& accept_languages, - const std::string& unsplit_spellcheck_dictionaries, - const std::string& unsplit_expected_languages, - const std::string& unsplit_expected_languages_used_for_spellcheck) + TestCase( + const std::string& accept_languages, + const std::vector<std::string>& spellcheck_dictionaries, + const std::vector<std::string>& expected_languages, + const std::vector<std::string>& expected_languages_used_for_spellcheck) : accept_languages(accept_languages), - spellcheck_dictionaries( - base::SplitString(unsplit_spellcheck_dictionaries, - ",", - base::TRIM_WHITESPACE, - base::SPLIT_WANT_ALL)) { - std::vector<std::string> languages = - base::SplitString(unsplit_expected_languages, ",", - base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); - - std::vector<std::string> used_for_spellcheck = - base::SplitString(unsplit_expected_languages_used_for_spellcheck, ",", - base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); - + spellcheck_dictionaries(spellcheck_dictionaries) { SpellcheckService::Dictionary dictionary; - for (const auto& language : languages) { - dictionary.language = language; - dictionary.used_for_spellcheck = - base::Contains(used_for_spellcheck, language); - expected_dictionaries.push_back(dictionary); + for (const auto& language : expected_languages) { + if (!language.empty()) { + dictionary.language = language; + dictionary.used_for_spellcheck = + base::Contains(expected_languages_used_for_spellcheck, language); + expected_dictionaries.push_back(dictionary); + } } } @@ -82,47 +79,74 @@ std::ostream& operator<<(std::ostream& out, const TestCase& test_case) { return out; } -class SpellcheckServiceUnitTest : public testing::TestWithParam<TestCase> { +static std::unique_ptr<KeyedService> BuildSpellcheckService( + content::BrowserContext* profile) { + return std::make_unique<SpellcheckService>(static_cast<Profile*>(profile)); +} + +class SpellcheckServiceUnitTestBase : public testing::Test { public: - SpellcheckServiceUnitTest() { - user_prefs::UserPrefs::Set(&context_, &prefs_); - } - ~SpellcheckServiceUnitTest() override {} + SpellcheckServiceUnitTestBase() = default; + ~SpellcheckServiceUnitTestBase() override = default; + + content::BrowserContext* browser_context() { return &profile_; } + PrefService* prefs() { return profile_.GetPrefs(); } + protected: void SetUp() override { - prefs()->registry()->RegisterListPref( - spellcheck::prefs::kSpellCheckDictionaries); - prefs()->registry()->RegisterStringPref(language::prefs::kAcceptLanguages, - std::string()); +#if defined(OS_WIN) + // Tests were designed assuming Hunspell dictionary used and may fail when + // Windows spellcheck is enabled by default. + feature_list_.InitAndDisableFeature(spellcheck::kWinUseBrowserSpellChecker); +#endif // defined(OS_WIN) + + // Use SetTestingFactoryAndUse to force creation and initialization. + SpellcheckServiceFactory::GetInstance()->SetTestingFactoryAndUse( + &profile_, base::BindRepeating(&BuildSpellcheckService)); } - base::SupportsUserData* context() { return &context_; } - TestingPrefServiceSimple* prefs() { return &prefs_; } + content::BrowserTaskEnvironment task_environment_; + +#if defined(OS_WIN) + // feature_list_ needs to be destroyed after profile_. + base::test::ScopedFeatureList feature_list_; +#endif // defined(OS_WIN) + TestingProfile profile_; private: - struct : public base::SupportsUserData { - } context_; - TestingPrefServiceSimple prefs_; - content::BrowserTaskEnvironment task_environment_; + DISALLOW_COPY_AND_ASSIGN(SpellcheckServiceUnitTestBase); +}; - DISALLOW_COPY_AND_ASSIGN(SpellcheckServiceUnitTest); +class SpellcheckServiceUnitTest : public SpellcheckServiceUnitTestBase, + public testing::WithParamInterface<TestCase> { }; INSTANTIATE_TEST_SUITE_P( TestCases, SpellcheckServiceUnitTest, testing::Values( - TestCase("en,aa", "aa", "", ""), - TestCase("en,en-JP,fr,aa", "fr", "fr", "fr"), - TestCase("en,en-JP,fr,zz,en-US", "fr", "fr,en-US", "fr"), - TestCase("en,en-US,en-GB", "en-GB", "en-US,en-GB", "en-GB"), - TestCase("en,en-US,en-AU", "en-AU", "en-US,en-AU", "en-AU"), - TestCase("en,en-US,en-AU", "en-US", "en-US,en-AU", "en-US"), - TestCase("en,en-US", "en-US", "en-US", "en-US"), - TestCase("en,en-US,fr", "en-US", "en-US,fr", "en-US"), - TestCase("en,fr,en-US,en-AU", "en-US,fr", "fr,en-US,en-AU", "fr,en-US"), - TestCase("en-US,en", "en-US", "en-US", "en-US"), - TestCase("hu-HU,hr-HR", "hr", "hu,hr", "hr"))); + TestCase("en,aa", {"aa"}, {""}, {""}), + TestCase("en,en-JP,fr,aa", {"fr"}, {"fr"}, {"fr"}), + TestCase("en,en-JP,fr,zz,en-US", {"fr"}, {"fr", "en-US"}, {"fr"}), + TestCase("en,en-US,en-GB", {"en-GB"}, {"en-US", "en-GB"}, {"en-GB"}), + TestCase("en,en-US,en-AU", {"en-AU"}, {"en-US", "en-AU"}, {"en-AU"}), + TestCase("en,en-US,en-AU", {"en-US"}, {"en-US", "en-AU"}, {"en-US"}), + TestCase("en,en-US", {"en-US"}, {"en-US"}, {"en-US"}), + TestCase("en,en-US,fr", {"en-US"}, {"en-US", "fr"}, {"en-US"}), + TestCase("en,fr,en-US,en-AU", + {"en-US", "fr"}, + {"fr", "en-US", "en-AU"}, + {"fr", "en-US"}), + TestCase("en-US,en", {"en-US"}, {"en-US"}, {"en-US"}), +#if defined(OS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER) + // Scenario where user disabled the Windows spellcheck feature with some + // non-Hunspell languages set in preferences. + TestCase("fr,eu,en-US,ar", + {"fr", "eu", "en-US", "ar"}, + {"fr", "en-US"}, + {"fr", "en-US"}), +#endif // defined(OS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER) + TestCase("hu-HU,hr-HR", {"hr"}, {"hu", "hr"}, {"hr"}))); TEST_P(SpellcheckServiceUnitTest, GetDictionaries) { prefs()->SetString(language::prefs::kAcceptLanguages, @@ -133,7 +157,340 @@ TEST_P(SpellcheckServiceUnitTest, GetDictionaries) { spellcheck_dictionaries); std::vector<SpellcheckService::Dictionary> dictionaries; - SpellcheckService::GetDictionaries(context(), &dictionaries); + SpellcheckService::GetDictionaries(browser_context(), &dictionaries); EXPECT_EQ(GetParam().expected_dictionaries, dictionaries); } + +#if defined(OS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER) +class SpellcheckServiceHybridUnitTestBase + : public SpellcheckServiceUnitTestBase { + public: + SpellcheckServiceHybridUnitTestBase() = default; + + protected: + void SetUp() override { + InitFeatures(); + + // Add command line switch that forces first run state, since code path + // through SpellcheckService::InitWindowsDictionaryLanguages depends on + // whether this is first run. + first_run::ResetCachedSentinelDataForTesting(); + base::CommandLine::ForCurrentProcess()->AppendSwitch( + switches::kForceFirstRun); + + // Use SetTestingFactoryAndUse to force creation and initialization. + SpellcheckServiceFactory::GetInstance()->SetTestingFactoryAndUse( + &profile_, base::BindRepeating(&BuildSpellcheckService)); + } + + virtual void InitFeatures() { + feature_list_.InitAndEnableFeature(spellcheck::kWinUseBrowserSpellChecker); + } + + virtual void InitializeSpellcheckService( + const std::vector<std::string>& spellcheck_languages_for_testing) { + // Fake the presence of Windows spellcheck dictionaries. + spellcheck_service_ = + SpellcheckServiceFactory::GetInstance()->GetForContext( + browser_context()); + + spellcheck_service_->InitWindowsDictionaryLanguages( + spellcheck_languages_for_testing); + + ASSERT_TRUE(spellcheck_service_->dictionaries_loaded()); + } + + void RunGetDictionariesTest( + const std::string accept_languages, + const std::vector<std::string> spellcheck_dictionaries, + const std::vector<SpellcheckService::Dictionary> expected_dictionaries); + + void RunDictionaryMappingTest( + const std::string full_tag, + const std::string expected_accept_language, + const std::string expected_tag_passed_to_spellcheck); + + // Used for faking the presence of Windows spellcheck dictionaries. + static const std::vector<std::string> + windows_spellcheck_languages_for_testing_; + + SpellcheckService* spellcheck_service_; +}; + +void SpellcheckServiceHybridUnitTestBase::RunGetDictionariesTest( + const std::string accept_languages, + const std::vector<std::string> spellcheck_dictionaries, + const std::vector<SpellcheckService::Dictionary> expected_dictionaries) { + if (!spellcheck::WindowsVersionSupportsSpellchecker()) + return; + + prefs()->SetString(language::prefs::kAcceptLanguages, accept_languages); + base::ListValue spellcheck_dictionaries_list; + spellcheck_dictionaries_list.AppendStrings(spellcheck_dictionaries); + prefs()->Set(spellcheck::prefs::kSpellCheckDictionaries, + spellcheck_dictionaries_list); + + InitializeSpellcheckService(windows_spellcheck_languages_for_testing_); + + std::vector<SpellcheckService::Dictionary> dictionaries; + SpellcheckService::GetDictionaries(browser_context(), &dictionaries); + + EXPECT_EQ(expected_dictionaries, dictionaries); +} + +void SpellcheckServiceHybridUnitTestBase::RunDictionaryMappingTest( + const std::string full_tag, + const std::string expected_accept_language, + const std::string expected_tag_passed_to_spellcheck) { + if (!spellcheck::WindowsVersionSupportsSpellchecker()) + return; + + InitializeSpellcheckService({full_tag}); + + std::string supported_accept_language = + SpellcheckService::GetSupportedAcceptLanguageCode(full_tag); + + EXPECT_EQ(expected_accept_language, supported_accept_language); + + if (!supported_accept_language.empty()) { + EXPECT_EQ(full_tag, + spellcheck_service_->GetSupportedWindowsDictionaryLanguage( + expected_accept_language)); + } else { + // Unsupported language--should not be in map. + ASSERT_TRUE( + spellcheck_service_->windows_spellcheck_dictionary_map_.empty()); + } + + EXPECT_EQ(expected_tag_passed_to_spellcheck, + SpellcheckService::GetTagToPassToWindowsSpellchecker( + expected_accept_language, full_tag)); + + // Special case for Serbian. The "sr" accept language is interpreted as using + // Cyrillic script. There should be an extra entry in the windows dictionary + // map if Cyrillic windows dictionary is installed. + if (base::EqualsCaseInsensitiveASCII( + "sr-Cyrl", SpellcheckService::GetLanguageAndScriptTag( + full_tag, + /* include_script_tag */ true))) { + EXPECT_EQ(full_tag, + spellcheck_service_->GetSupportedWindowsDictionaryLanguage("sr")); + } else { + EXPECT_TRUE(spellcheck_service_->GetSupportedWindowsDictionaryLanguage("sr") + .empty()); + } +} + +// static +const std::vector<std::string> SpellcheckServiceHybridUnitTestBase:: + windows_spellcheck_languages_for_testing_ = { + "fr-FR", // Has both Windows and Hunspell support. + "es-MX", // Has both Windows and Hunspell support, but for Hunspell + // maps to es-ES. + "gl-ES", // (Galician) Has only Windows support, no Hunspell + // dictionary. + "fi-FI", // (Finnish) Has only Windows support, no Hunspell + // dictionary. + "haw-US", // (Hawaiian) No Hunspell dictionary. Note that first two + // letters of language code are "ha," the same as Hausa. + "ast", // (Asturian) Has only Windows support, no Hunspell + // dictionary. Note that last two letters of language + // code are "st," the same as Sesotho. + "kok-Deva-IN", // Konkani (Devanagari, India)--note presence of + // script subtag. + "sr-Cyrl-ME", // Serbian (Cyrillic, Montenegro)--note presence of + // script subtag. + "sr-Latn-ME", // Serbian (Latin, Montenegro)--note presence of + // script subtag. + "ja-Latn-JP-x-ext", // Japanese with Latin script--note presence of + // private use subtag. Ignore private use + // dictionaries. +}; + +class SpellcheckServiceHybridUnitTest + : public SpellcheckServiceHybridUnitTestBase, + public testing::WithParamInterface<TestCase> {}; + +static const TestCase kHybridGetDictionariesParams[] = { + // Galician (gl) has only Windows support, no Hunspell dictionary. Croatian + // (hr) has only Hunspell support, no local Windows dictionary. First + // language is supported by windows and should be spellchecked + TestCase("gl", {""}, {"gl"}, {"gl"}), + TestCase("gl", {"gl"}, {"gl"}, {"gl"}), + TestCase("gl,hr", {""}, {"gl", "hr"}, {"gl"}), + TestCase("gl,hr", {"gl"}, {"gl", "hr"}, {"gl"}), + TestCase("gl,hr", {"hr"}, {"gl", "hr"}, {"gl", "hr"}), + TestCase("gl,hr", {"gl", "hr"}, {"gl", "hr"}, {"gl", "hr"}), + // First language is not supported by windows so nothing is changed + TestCase("hr", {""}, {"hr"}, {""}), TestCase("hr", {"hr"}, {"hr"}, {"hr"}), + TestCase("hr,gl", {"hr"}, {"hr", "gl"}, {"hr"}), + // Finnish has only "fi" in hard-coded list of accept languages. + TestCase("fi-FI,fi,en-US,en", {"en-US"}, {"fi", "en-US"}, {"fi", "en-US"}), + // First language is supported by Windows but private use dictionaries + // are ignored. + TestCase("ja,gl", {"gl"}, {"gl"}, {"gl"}), + // (Basque) No Hunspell support, has Windows support but + // language pack not present. + TestCase("eu", {"eu"}, {""}, {""}), + TestCase("es-419,es-MX", + {"es-419", "es-MX"}, + {"es-419", "es-MX"}, + {"es-419", "es-MX"}), + TestCase("fr-FR,es-MX,gl,pt-BR,hr,it", + {"fr-FR", "gl", "pt-BR", "it"}, + {"fr-FR", "es-MX", "gl", "pt-BR", "hr", "it"}, + {"fr-FR", "gl", "pt-BR", "it"}), + // Hausa with Hawaiian language pack (ha/haw string in string). + TestCase("ha", {"ha"}, {""}, {""}), + // Sesotho with Asturian language pack (st/ast string in string). + TestCase("st", {"st"}, {""}, {""}), + // User chose generic Serbian in languages preferences (which uses + // Cyrillic script). + TestCase("sr,sr-Latn-RS", {"sr", "sr-Latn-RS"}, {"sr"}, {"sr"})}; + +INSTANTIATE_TEST_SUITE_P(TestCases, + SpellcheckServiceHybridUnitTest, + testing::ValuesIn(kHybridGetDictionariesParams)); + +TEST_P(SpellcheckServiceHybridUnitTest, GetDictionaries) { + RunGetDictionariesTest(GetParam().accept_languages, + GetParam().spellcheck_dictionaries, + GetParam().expected_dictionaries); +} + +struct DictionaryMappingTestCase { + std::string full_tag; + std::string expected_accept_language; + std::string expected_tag_passed_to_spellcheck; +}; + +std::ostream& operator<<(std::ostream& out, + const DictionaryMappingTestCase& test_case) { + out << "full_tag=" << test_case.full_tag + << ", expected_accept_language=" << test_case.expected_accept_language + << ", expected_tag_passed_to_spellcheck=" + << test_case.expected_tag_passed_to_spellcheck; + + return out; +} + +class SpellcheckServiceWindowsDictionaryMappingUnitTest + : public SpellcheckServiceHybridUnitTestBase, + public testing::WithParamInterface<DictionaryMappingTestCase> {}; + +static const DictionaryMappingTestCase kHybridDictionaryMappingsParams[] = { + DictionaryMappingTestCase({"en-CA", "en-CA", "en-CA"}), + DictionaryMappingTestCase({"en-PH", "en", "en"}), + DictionaryMappingTestCase({"es-MX", "es-MX", "es-MX"}), + DictionaryMappingTestCase({"ar-SA", "ar", "ar"}), + // Konkani not supported in Chromium. + DictionaryMappingTestCase({"kok-Deva-IN", "", "kok-Deva"}), + DictionaryMappingTestCase({"sr-Cyrl-RS", "sr", "sr-Cyrl"}), + DictionaryMappingTestCase({"sr-Cyrl-ME", "sr", "sr-Cyrl"}), + // Only sr with Cyrillic implied supported in Chromium. + DictionaryMappingTestCase({"sr-Latn-RS", "", "sr-Latn"}), + DictionaryMappingTestCase({"sr-Latn-ME", "", "sr-Latn"}), + DictionaryMappingTestCase({"ca-ES", "ca", "ca"}), + DictionaryMappingTestCase({"ca-ES-valencia", "ca", "ca"})}; + +INSTANTIATE_TEST_SUITE_P(TestCases, + SpellcheckServiceWindowsDictionaryMappingUnitTest, + testing::ValuesIn(kHybridDictionaryMappingsParams)); + +TEST_P(SpellcheckServiceWindowsDictionaryMappingUnitTest, CheckMappings) { + RunDictionaryMappingTest(GetParam().full_tag, + GetParam().expected_accept_language, + GetParam().expected_tag_passed_to_spellcheck); +} + +class SpellcheckServiceHybridUnitTestDelayInitBase + : public SpellcheckServiceHybridUnitTestBase { + public: + SpellcheckServiceHybridUnitTestDelayInitBase() = default; + + void OnDictionariesInitialized() { + dictionaries_initialized_received_ = true; + if (quit_) + std::move(quit_).Run(); + } + + protected: + void InitFeatures() override { + // Don't initialize the SpellcheckService on browser launch. + feature_list_.InitWithFeatures( + /*enabled_features=*/{spellcheck::kWinUseBrowserSpellChecker, + spellcheck::kWinDelaySpellcheckServiceInit}, + /*disabled_features=*/{}); + } + + void InitializeSpellcheckService( + const std::vector<std::string>& spellcheck_languages_for_testing) + override { + // Fake the presence of Windows spellcheck dictionaries. + spellcheck_service_ = + SpellcheckServiceFactory::GetInstance()->GetForContext( + browser_context()); + + spellcheck_service_->AddSpellcheckLanguagesForTesting( + spellcheck_languages_for_testing); + + // Asynchronously load the dictionaries. + ASSERT_FALSE(spellcheck_service_->dictionaries_loaded()); + spellcheck_service_->InitializeDictionaries( + base::BindOnce(&SpellcheckServiceHybridUnitTestDelayInitBase:: + OnDictionariesInitialized, + base::Unretained(this))); + + RunUntilCallbackReceived(); + ASSERT_TRUE(spellcheck_service_->dictionaries_loaded()); + } + + void RunUntilCallbackReceived() { + if (dictionaries_initialized_received_) + return; + base::RunLoop run_loop; + quit_ = run_loop.QuitClosure(); + run_loop.Run(); + + // reset status. + dictionaries_initialized_received_ = false; + } + + private: + bool dictionaries_initialized_received_ = false; + + // Quits the RunLoop on receiving the callback from InitializeDictionaries. + base::OnceClosure quit_; +}; + +class SpellcheckServiceHybridUnitTestDelayInit + : public SpellcheckServiceHybridUnitTestDelayInitBase, + public testing::WithParamInterface<TestCase> {}; + +INSTANTIATE_TEST_SUITE_P(TestCases, + SpellcheckServiceHybridUnitTestDelayInit, + testing::ValuesIn(kHybridGetDictionariesParams)); + +TEST_P(SpellcheckServiceHybridUnitTestDelayInit, GetDictionaries) { + RunGetDictionariesTest(GetParam().accept_languages, + GetParam().spellcheck_dictionaries, + GetParam().expected_dictionaries); +} + +class SpellcheckServiceWindowsDictionaryMappingUnitTestDelayInit + : public SpellcheckServiceHybridUnitTestDelayInitBase, + public testing::WithParamInterface<DictionaryMappingTestCase> {}; + +INSTANTIATE_TEST_SUITE_P( + TestCases, + SpellcheckServiceWindowsDictionaryMappingUnitTestDelayInit, + testing::ValuesIn(kHybridDictionaryMappingsParams)); + +TEST_P(SpellcheckServiceWindowsDictionaryMappingUnitTestDelayInit, + CheckMappings) { + RunDictionaryMappingTest(GetParam().full_tag, + GetParam().expected_accept_language, + GetParam().expected_tag_passed_to_spellcheck); +} +#endif // defined(OS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER) diff --git a/chromium/chrome/browser/spellchecker/spelling_request.cc b/chromium/chrome/browser/spellchecker/spelling_request.cc index 37e993acff9..d3029666f29 100644 --- a/chromium/chrome/browser/spellchecker/spelling_request.cc +++ b/chromium/chrome/browser/spellchecker/spelling_request.cc @@ -7,7 +7,6 @@ #include "base/barrier_closure.h" #include "base/bind.h" #include "base/strings/utf_string_conversions.h" -#include "base/task/post_task.h" #include "chrome/browser/spellchecker/spellcheck_custom_dictionary.h" #include "chrome/browser/spellchecker/spellcheck_factory.h" #include "components/spellcheck/browser/spellcheck_platform.h" @@ -133,8 +132,8 @@ void SpellingRequest::OnLocalCheckCompletedOnAnyThread( base::WeakPtr<SpellingRequest> request, const std::vector<SpellCheckResult>& results) { // Local checking can happen on any thread - don't DCHECK thread. - base::PostTask(FROM_HERE, {content::BrowserThread::UI}, - base::BindOnce(&SpellingRequest::OnLocalCheckCompleted, + content::GetUIThreadTaskRunner({})->PostTask( + FROM_HERE, base::BindOnce(&SpellingRequest::OnLocalCheckCompleted, request, results)); } |