summaryrefslogtreecommitdiff
path: root/chromium/chrome/browser/spellchecker/spellcheck_service.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/chrome/browser/spellchecker/spellcheck_service.cc')
-rw-r--r--chromium/chrome/browser/spellchecker/spellcheck_service.cc352
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)