// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/spellchecker/spellcheck_service.h" #include #include #include #include #include "base/bind.h" #include "base/check_op.h" #include "base/no_destructor.h" #include "base/stl_util.h" #include "base/strings/string_split.h" #include "base/supports_user_data.h" #include "base/synchronization/waitable_event.h" #include "base/values.h" #include "build/build_config.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" #include "components/spellcheck/browser/spelling_service_client.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/spellcheck_buildflags.h" #include "components/user_prefs/user_prefs.h" #include "content/public/browser/browser_context.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/notification_service.h" #include "content/public/browser/notification_types.h" #include "content/public/browser/render_process_host.h" #include "content/public/browser/storage_partition.h" #include "mojo/public/cpp/bindings/remote.h" #include "ui/base/l10n/l10n_util.h" #if defined(OS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER) #include "base/task/post_task.h" #include "base/task/task_traits.h" #include "base/task/thread_pool.h" #include "components/spellcheck/browser/windows_spell_checker.h" #endif // defined(OS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER) using content::BrowserThread; namespace { SpellcheckService::SpellCheckerBinder& GetSpellCheckerBinderOverride() { static base::NoDestructor binder; return *binder; } } // namespace // TODO(rlp): I do not like globals, but keeping these for now during // transition. // An event used by browser tests to receive status events from this class and // its derived classes. base::WaitableEvent* g_status_event = NULL; SpellcheckService::EventType g_status_type = SpellcheckService::BDICT_NOTINITIALIZED; SpellcheckService::SpellcheckService(content::BrowserContext* context) : context_(context) { DCHECK_CURRENTLY_ON(BrowserThread::UI); PrefService* prefs = user_prefs::UserPrefs::Get(context); pref_change_registrar_.Init(prefs); StringListPrefMember dictionaries_pref; dictionaries_pref.Init(spellcheck::prefs::kSpellCheckDictionaries, prefs); std::string first_of_dictionaries; #if (defined(OS_MAC) || defined(OS_ANDROID)) && !defined(TOOLKIT_QT) // Ensure that the renderer always knows the platform spellchecking // language. This language is used for initialization of the text iterator. // If the iterator is not initialized, then the context menu does not show // spellcheck suggestions. // No migration is necessary, because the spellcheck language preference is // not user visible or modifiable in Chrome on Mac. dictionaries_pref.SetValue(std::vector( 1, spellcheck_platform::GetSpellCheckerLanguage())); first_of_dictionaries = dictionaries_pref.GetValue().front(); #else // Migrate preferences from single-language to multi-language schema. StringPrefMember single_dictionary_pref; single_dictionary_pref.Init(spellcheck::prefs::kSpellCheckDictionary, prefs); std::string single_dictionary = single_dictionary_pref.GetValue(); if (!dictionaries_pref.GetValue().empty()) first_of_dictionaries = dictionaries_pref.GetValue().front(); if (first_of_dictionaries.empty() && !single_dictionary.empty()) { first_of_dictionaries = single_dictionary; dictionaries_pref.SetValue( std::vector(1, first_of_dictionaries)); } single_dictionary_pref.SetValue(""); #endif // defined(OS_MAC) || 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, base::Unretained(this))); pref_change_registrar_.Add( spellcheck::prefs::kSpellCheckForcedDictionaries, base::BindRepeating(&SpellcheckService::OnSpellCheckDictionariesChanged, base::Unretained(this))); pref_change_registrar_.Add( spellcheck::prefs::kSpellCheckBlocklistedDictionaries, base::BindRepeating(&SpellcheckService::OnSpellCheckDictionariesChanged, base::Unretained(this))); pref_change_registrar_.Add( spellcheck::prefs::kSpellCheckUseSpellingService, base::BindRepeating(&SpellcheckService::OnUseSpellingServiceChanged, base::Unretained(this))); pref_change_registrar_.Add( language::prefs::kAcceptLanguages, base::BindRepeating(&SpellcheckService::OnAcceptLanguagesChanged, base::Unretained(this))); pref_change_registrar_.Add( spellcheck::prefs::kSpellCheckEnable, base::BindRepeating(&SpellcheckService::InitForAllRenderers, base::Unretained(this))); custom_dictionary_.reset(new SpellcheckCustomDictionary(context_->GetPath())); custom_dictionary_->AddObserver(this); custom_dictionary_->Load(); registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_CREATED, content::NotificationService::AllSources()); #if defined(OS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER) 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() { // Remove pref observers pref_change_registrar_.RemoveAll(); } base::WeakPtr SpellcheckService::GetWeakPtr() { return weak_ptr_factory_.GetWeakPtr(); } #if !defined(OS_MAC) // static void SpellcheckService::GetDictionaries( content::BrowserContext* browser_context, std::vector* dictionaries) { PrefService* prefs = user_prefs::UserPrefs::Get(browser_context); std::set spellcheck_dictionaries; for (const auto& value : *prefs->GetList(spellcheck::prefs::kSpellCheckDictionaries)) { std::string dictionary; if (value.GetAsString(&dictionary)) spellcheck_dictionaries.insert(dictionary); } dictionaries->clear(); std::vector accept_languages = base::SplitString(prefs->GetString(language::prefs::kAcceptLanguages), ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); for (const auto& accept_language : accept_languages) { Dictionary dictionary; #if defined(OS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER) 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; dictionary.used_for_spellcheck = spellcheck_dictionaries.count(dictionary.language) > 0; dictionaries->push_back(dictionary); } } #endif // !OS_MAC // static bool SpellcheckService::SignalStatusEvent( SpellcheckService::EventType status_type) { DCHECK_CURRENTLY_ON(BrowserThread::UI); if (!g_status_event) return false; g_status_type = status_type; g_status_event->Signal(); return true; } // static std::string SpellcheckService::GetSupportedAcceptLanguageCode( const std::string& supported_language_full_tag, bool generic_only) { // Default to accept language in hardcoded list of Hunspell dictionaries // (kSupportedSpellCheckerLanguages). std::string supported_accept_language = spellcheck::GetCorrespondingSpellCheckLanguage( supported_language_full_tag); if (generic_only) { supported_accept_language = SpellcheckService::GetLanguageAndScriptTag( supported_accept_language, /* include_script_tag= */ false); } #if defined(OS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER) 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. std::vector accept_languages; l10n_util::GetAcceptLanguages(&accept_languages); if (generic_only) { return GetSupportedAcceptLanguageCodeGenericOnly( supported_language_full_tag, accept_languages); } // 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). auto iter = std::find_if(accept_languages.begin(), accept_languages.end(), [supported_language_full_tag](const auto& accept_language) { return base::EqualsCaseInsensitiveASCII( supported_language_full_tag, accept_language); }); if (iter != accept_languages.end()) return *iter; // Then try matching just the language and (optional) script subtags, but // not the region subtag. For example, Edge supports sr-Cyrl-RS as an accept // language, but not sr-Cyrl-CS. Matching language + script subtags assures // we get the correct script for spellchecking, and not use sr-Latn-RS if // language packs for both scripts are installed on the system. if (!base::Contains(supported_language_full_tag, "-")) return ""; iter = std::find_if(accept_languages.begin(), accept_languages.end(), [supported_language_full_tag](const auto& accept_language) { return base::EqualsCaseInsensitiveASCII( SpellcheckService::GetLanguageAndScriptTag( supported_language_full_tag, /* include_script_tag= */ true), SpellcheckService::GetLanguageAndScriptTag( accept_language, /* include_script_tag= */ true)); }); if (iter != accept_languages.end()) return *iter; // Then try just matching the leading language subtag. E.g. Edge supports // kok as an accept language, but if the Konkani language pack is // installed the Windows spellcheck API reports kok-Deva-IN for the // dictionary name. return GetSupportedAcceptLanguageCodeGenericOnly(supported_language_full_tag, accept_languages); #endif // defined(OS_WIN) return supported_accept_language; } #if defined(OS_WIN) // static void SpellcheckService::EnableFirstUserLanguageForSpellcheck( PrefService* prefs) { // Ensure that spellcheck is enabled for the first language in the // accept languages list. base::Value user_dictionaries = prefs->GetList(spellcheck::prefs::kSpellCheckDictionaries)->Clone(); std::vector 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 Finnish 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 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) { user_dictionaries.Insert(user_dictionaries.GetList().begin(), base::Value(first_user_language)); prefs->Set(spellcheck::prefs::kSpellCheckDictionaries, user_dictionaries); } } #endif // defined(OS_WIN) void SpellcheckService::StartRecordingMetrics(bool spellcheck_enabled) { metrics_ = std::make_unique(); metrics_->RecordEnabledStats(spellcheck_enabled); OnUseSpellingServiceChanged(); #if defined(OS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER) RecordChromeLocalesStats(); RecordSpellcheckLocalesStats(); #endif // defined(OS_WIN) } void SpellcheckService::InitForRenderer(content::RenderProcessHost* host) { DCHECK_CURRENTLY_ON(BrowserThread::UI); content::BrowserContext* context = host->GetBrowserContext(); if (SpellcheckServiceFactory::GetForContext(context) != this) return; const bool enable = IsSpellcheckEnabled(); std::vector dictionaries; std::vector custom_words; if (enable) { for (const auto& hunspell_dictionary : hunspell_dictionaries_) { dictionaries.push_back(spellcheck::mojom::SpellCheckBDictLanguage::New( hunspell_dictionary->GetDictionaryFile().Duplicate(), hunspell_dictionary->GetLanguage())); } custom_words.assign(custom_dictionary_->GetWords().begin(), custom_dictionary_->GetWords().end()); } else { // Disabling spell check should also disable spelling service. user_prefs::UserPrefs::Get(context)->SetBoolean( spellcheck::prefs::kSpellCheckUseSpellingService, false); } GetSpellCheckerForProcess(host)->Initialize(std::move(dictionaries), custom_words, enable); } SpellCheckHostMetrics* SpellcheckService::GetMetrics() const { return metrics_.get(); } SpellcheckCustomDictionary* SpellcheckService::GetCustomDictionary() { return custom_dictionary_.get(); } void SpellcheckService::LoadDictionaries() { hunspell_dictionaries_.clear(); PrefService* prefs = user_prefs::UserPrefs::Get(context_); DCHECK(prefs); const base::ListValue* user_dictionaries = prefs->GetList(spellcheck::prefs::kSpellCheckDictionaries); const base::ListValue* forced_dictionaries = prefs->GetList(spellcheck::prefs::kSpellCheckForcedDictionaries); // Build a lookup of blocked dictionaries to skip loading them. const base::ListValue* blocked_dictionaries = prefs->GetList(spellcheck::prefs::kSpellCheckBlocklistedDictionaries); std::unordered_set blocked_dictionaries_lookup; for (const auto& blocked_dict : blocked_dictionaries->GetList()) { blocked_dictionaries_lookup.insert(blocked_dict.GetString()); } // Merge both lists of dictionaries. Use a set to avoid duplicates. std::set dictionaries; for (const auto& dictionary_value : user_dictionaries->GetList()) { if (blocked_dictionaries_lookup.find(dictionary_value.GetString()) == blocked_dictionaries_lookup.end()) dictionaries.insert(dictionary_value.GetString()); } for (const auto& dictionary_value : forced_dictionaries->GetList()) { dictionaries.insert(dictionary_value.GetString()); } 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( dictionary, platform_spellcheck_language, context_, this)); hunspell_dictionaries_.back()->AddObserver(this); hunspell_dictionaries_.back()->Load(); } #if defined(OS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER) RecordSpellcheckLocalesStats(); 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 // defined(OS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER) dictionaries_loaded_ = true; } const std::vector>& SpellcheckService::GetHunspellDictionaries() { return hunspell_dictionaries_; } bool SpellcheckService::IsSpellcheckEnabled() const { const PrefService* prefs = user_prefs::UserPrefs::Get(context_); bool enable_if_uninitialized = false; #if defined(OS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER) if (spellcheck::UseBrowserSpellChecker() && base::FeatureList::IsEnabled( spellcheck::kWinDelaySpellcheckServiceInit)) { // If initialization of the spellcheck service is on-demand, the // renderer-side SpellCheck object needs to start out as enabled in order // for a click on editable content to initialize the spellcheck service. if (!dictionaries_loaded()) enable_if_uninitialized = true; } #endif // defined(OS_WIN) return prefs->GetBoolean(spellcheck::prefs::kSpellCheckEnable) && (!hunspell_dictionaries_.empty() || enable_if_uninitialized); } bool SpellcheckService::LoadExternalDictionary(std::string language, std::string locale, std::string path, DictionaryFormat format) { return false; } bool SpellcheckService::UnloadExternalDictionary( const std::string& /* path */) { return false; } void SpellcheckService::Observe(int type, const content::NotificationSource& source, const content::NotificationDetails& details) { DCHECK_EQ(content::NOTIFICATION_RENDERER_PROCESS_CREATED, type); InitForRenderer(content::Source(source).ptr()); } void SpellcheckService::OnCustomDictionaryLoaded() { InitForAllRenderers(); } void SpellcheckService::OnCustomDictionaryChanged( const SpellcheckCustomDictionary::Change& change) { DCHECK_CURRENTLY_ON(BrowserThread::UI); const std::vector additions(change.to_add().begin(), change.to_add().end()); const std::vector deletions(change.to_remove().begin(), change.to_remove().end()); for (content::RenderProcessHost::iterator it( content::RenderProcessHost::AllHostsIterator()); !it.IsAtEnd(); it.Advance()) { content::RenderProcessHost* process = it.GetCurrentValue(); if (!process->IsInitializedAndNotDead()) continue; GetSpellCheckerForProcess(process)->CustomDictionaryChanged(additions, deletions); } } void SpellcheckService::OnHunspellDictionaryInitialized( const std::string& language) { InitForAllRenderers(); } void SpellcheckService::OnHunspellDictionaryDownloadBegin( const std::string& language) { } void SpellcheckService::OnHunspellDictionaryDownloadSuccess( const std::string& language) { } void SpellcheckService::OnHunspellDictionaryDownloadFailure( const std::string& language) { #ifdef TOOLKIT_QT DCHECK_CURRENTLY_ON(BrowserThread::UI); context_->FailedToLoadDictionary(language); #endif } 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) && BUILDFLAG(USE_BROWSER_SPELLCHECKER) 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) && BUILDFLAG(USE_BROWSER_SPELLCHECKER) 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) && BUILDFLAG(USE_BROWSER_SPELLCHECKER) void SpellcheckService::InitWindowsDictionaryLanguages( const std::vector& 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, /* generic_only */ false); 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); } // Add the generic language with the region subtag removed too if it exists // in the list of accept languages, and use it when calling the Windows // spellcheck APIs. For example, if the preferred language settings include // just generic Portuguese (pt), but the Portuguese (Brazil) platform // language pack (pt-BR) is installed, we want an entry for it so that the // generic Portuguese language can be enabled for spellchecking. The Windows // platform spellcheck API has logic to load the pt-BR dictionary if only pt // is specified as the BCP47 language tag. The use of a map in // AddWindowsSpellcheckDictionary ensures there won't be duplicate entries // if a generic language was already added above (ar-SA would already be // mapped to ar since the accept language ar-SA is not recognized by the // browser e.g.). accept_language = SpellcheckService::GetSupportedAcceptLanguageCode( windows_spellcheck_language, /* generic_only */ true); AddWindowsSpellcheckDictionary(accept_language, accept_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. This also handles the case where the // primary preferred language is enabled for spellchecking during first run, // but it's now determined that there is neither Windows platform nor Hunspell // dictionary support for that language. 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()); }); // 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); } // static std::string SpellcheckService::GetLanguageAndScriptTag( const std::string& full_tag, bool include_script_tag) { if (full_tag.empty()) return ""; std::string language_and_script_tag; std::vector subtags = base::SplitString( full_tag, "-", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); // Language subtag is required, all others optional. DCHECK_GE(subtags.size(), 1ULL); std::vector subtag_tokens_to_pass; subtag_tokens_to_pass.push_back(subtags.front()); subtags.erase(subtags.begin()); // The optional script subtag always follows the language subtag, and is 4 // characters in length. if (include_script_tag) { if (!subtags.empty() && subtags.front().length() == 4) { subtag_tokens_to_pass.push_back(subtags.front()); } } language_and_script_tag = base::JoinString(subtag_tokens_to_pass, "-"); return language_and_script_tag; } #if defined(OS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER) // static std::string SpellcheckService::GetSupportedAcceptLanguageCodeGenericOnly( const std::string& supported_language_full_tag, const std::vector& accept_languages) { auto iter = std::find_if(accept_languages.begin(), accept_languages.end(), [supported_language_full_tag](const auto& accept_language) { return base::EqualsCaseInsensitiveASCII( SpellcheckService::GetLanguageAndScriptTag( supported_language_full_tag, /* include_script_tag= */ false), SpellcheckService::GetLanguageAndScriptTag( accept_language, /* include_script_tag= */ false)); }); 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; } return ""; } // static bool SpellcheckService::HasPrivateUseSubTag(const std::string& full_tag) { std::vector 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"); } // 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); g_status_event = status_event; } // static SpellcheckService::EventType SpellcheckService::GetStatusEvent() { DCHECK_CURRENTLY_ON(BrowserThread::UI); return g_status_type; } mojo::Remote SpellcheckService::GetSpellCheckerForProcess(content::RenderProcessHost* host) { mojo::Remote spellchecker; auto receiver = spellchecker.BindNewPipeAndPassReceiver(); auto binder = GetSpellCheckerBinderOverride(); if (binder) binder.Run(std::move(receiver)); else host->BindReceiver(std::move(receiver)); return spellchecker; } void SpellcheckService::InitForAllRenderers() { DCHECK_CURRENTLY_ON(BrowserThread::UI); for (content::RenderProcessHost::iterator i( content::RenderProcessHost::AllHostsIterator()); !i.IsAtEnd(); i.Advance()) { content::RenderProcessHost* process = i.GetCurrentValue(); if (process && process->GetProcess().Handle()) InitForRenderer(process); } } void SpellcheckService::OnSpellCheckDictionariesChanged() { // If there are hunspell dictionaries, then fire off notifications to the // renderers after the dictionaries are finished loading. LoadDictionaries(); // If there are no hunspell dictionaries to load, then immediately let the // renderers know the new state. if (hunspell_dictionaries_.empty()) { #if !defined(OS_MAC) // Only update non-MacOS platform because basic spell check on Mac OS // is controlled by OS and doesn't depend on users' dictionaries pref user_prefs::UserPrefs::Get(context_)->SetBoolean( spellcheck::prefs::kSpellCheckEnable, false); #endif // !defined(OS_MAC) InitForAllRenderers(); } } void SpellcheckService::OnUseSpellingServiceChanged() { bool enabled = pref_change_registrar_.prefs()->GetBoolean( spellcheck::prefs::kSpellCheckUseSpellingService); if (metrics_) metrics_->RecordSpellingServiceStats(enabled); } void SpellcheckService::OnAcceptLanguagesChanged() { std::vector accept_languages = GetNormalizedAcceptLanguages(); StringListPrefMember dictionaries_pref; dictionaries_pref.Init(spellcheck::prefs::kSpellCheckDictionaries, user_prefs::UserPrefs::Get(context_)); std::vector dictionaries = dictionaries_pref.GetValue(); std::vector filtered_dictionaries; for (const auto& dictionary : dictionaries) { if (base::Contains(accept_languages, dictionary)) { filtered_dictionaries.push_back(dictionary); } } dictionaries_pref.SetValue(filtered_dictionaries); #if defined(OS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER) RecordChromeLocalesStats(); #endif // defined(OS_WIN) } std::vector SpellcheckService::GetNormalizedAcceptLanguages( bool normalize_for_spellcheck) const { PrefService* prefs = user_prefs::UserPrefs::Get(context_); std::vector accept_languages = base::SplitString(prefs->GetString(language::prefs::kAcceptLanguages), ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); if (normalize_for_spellcheck) { std::transform( accept_languages.begin(), accept_languages.end(), accept_languages.begin(), [&](const std::string& language) { #if defined(OS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER) if (spellcheck::UseBrowserSpellChecker() && UsesWindowsDictionary(language)) return language; #endif // defined(OS_WIN) return spellcheck::GetCorrespondingSpellCheckLanguage(language); }); } return accept_languages; } #if defined(OS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER) 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( base::ThreadPool::CreateCOMSTATaskRunner({base::MayBlock()})); } } void SpellcheckService::RecordSpellcheckLocalesStats() { if (spellcheck::WindowsVersionSupportsSpellchecker() && metrics_ && platform_spell_checker() && !hunspell_dictionaries_.empty()) { std::vector hunspell_locales; for (auto& dict : hunspell_dictionaries_) { hunspell_locales.push_back(dict->GetLanguage()); } spellcheck_platform::RecordSpellcheckLocalesStats( platform_spell_checker(), std::move(hunspell_locales), metrics_.get()); } } void SpellcheckService::RecordChromeLocalesStats() { const auto& accept_languages = GetNormalizedAcceptLanguages(/* normalize_for_spellcheck */ false); if (spellcheck::WindowsVersionSupportsSpellchecker() && metrics_ && platform_spell_checker() && !accept_languages.empty()) { spellcheck_platform::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& languages) { InitializePlatformSpellchecker(); if (platform_spell_checker()) { spellcheck_platform::AddSpellcheckLanguagesForTesting( platform_spell_checker(), languages); } } #endif // defined(OS_WIN)