diff options
Diffstat (limited to 'chromium/chrome/browser/spellchecker/spellcheck_service.cc')
-rw-r--r-- | chromium/chrome/browser/spellchecker/spellcheck_service.cc | 352 |
1 files changed, 328 insertions, 24 deletions
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) |