// 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 "base/logging.h" #include "base/strings/string_split.h" #include "base/supports_user_data.h" #include "base/synchronization/waitable_event.h" #include "build/build_config.h" #include "chrome/browser/spellchecker/spellcheck_factory.h" #include "chrome/browser/spellchecker/spellcheck_hunspell_dictionary.h" #include "chrome/common/pref_names.h" #include "components/prefs/pref_member.h" #include "components/prefs/pref_service.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/spellcheck_build_features.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" using content::BrowserThread; // 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), weak_ptr_factory_(this) { 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 BUILDFLAG(USE_BROWSER_SPELLCHECKER) // 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 // BUILDFLAG(USE_BROWSER_SPELLCHECKER) pref_change_registrar_.Add( spellcheck::prefs::kSpellCheckDictionaries, base::Bind(&SpellcheckService::OnSpellCheckDictionariesChanged, base::Unretained(this))); pref_change_registrar_.Add( spellcheck::prefs::kSpellCheckUseSpellingService, base::Bind(&SpellcheckService::OnUseSpellingServiceChanged, base::Unretained(this))); pref_change_registrar_.Add( prefs::kAcceptLanguages, base::Bind(&SpellcheckService::OnAcceptLanguagesChanged, base::Unretained(this))); pref_change_registrar_.Add(spellcheck::prefs::kEnableSpellcheck, base::Bind(&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()); LoadHunspellDictionaries(); } SpellcheckService::~SpellcheckService() { // Remove pref observers pref_change_registrar_.RemoveAll(); } base::WeakPtr SpellcheckService::GetWeakPtr() { return weak_ptr_factory_.GetWeakPtr(); } #if !defined(OS_MACOSX) // static void SpellcheckService::GetDictionaries(base::SupportsUserData* 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(prefs::kAcceptLanguages), ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); for (const auto& accept_language : accept_languages) { Dictionary dictionary; dictionary.language = spellcheck::GetCorrespondingSpellCheckLanguage(accept_language); if (dictionary.language.empty()) continue; dictionary.used_for_spellcheck = spellcheck_dictionaries.count(dictionary.language) > 0; dictionaries->push_back(dictionary); } } #endif // !OS_MACOSX // 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; } void SpellcheckService::StartRecordingMetrics(bool spellcheck_enabled) { metrics_.reset(new SpellCheckHostMetrics()); metrics_->RecordEnabledStats(spellcheck_enabled); OnUseSpellingServiceChanged(); } void SpellcheckService::InitForRenderer(content::RenderProcessHost* process) { DCHECK_CURRENTLY_ON(BrowserThread::UI); content::BrowserContext* context = process->GetBrowserContext(); if (SpellcheckServiceFactory::GetForContext(context) != this) return; const PrefService* prefs = user_prefs::UserPrefs::Get(context); std::vector dictionaries; for (const auto& hunspell_dictionary : hunspell_dictionaries_) { dictionaries.push_back(spellcheck::mojom::SpellCheckBDictLanguage::New( hunspell_dictionary->GetDictionaryFile().Duplicate(), hunspell_dictionary->GetLanguage())); } bool enable = prefs->GetBoolean(spellcheck::prefs::kEnableSpellcheck) && !dictionaries.empty(); std::vector custom_words; if (enable) { custom_words.assign(custom_dictionary_->GetWords().begin(), custom_dictionary_->GetWords().end()); } spellcheck::mojom::SpellCheckerPtr spellchecker; content::BindInterface(process, &spellchecker); spellchecker->Initialize(std::move(dictionaries), custom_words, enable); } SpellCheckHostMetrics* SpellcheckService::GetMetrics() const { return metrics_.get(); } SpellcheckCustomDictionary* SpellcheckService::GetCustomDictionary() { return custom_dictionary_.get(); } void SpellcheckService::LoadHunspellDictionaries() { hunspell_dictionaries_.clear(); PrefService* prefs = user_prefs::UserPrefs::Get(context_); DCHECK(prefs); const base::ListValue* dictionary_values = prefs->GetList(spellcheck::prefs::kSpellCheckDictionaries); for (const auto& dictionary_value : *dictionary_values) { std::string dictionary; dictionary_value.GetAsString(&dictionary); hunspell_dictionaries_.push_back( base::MakeUnique( dictionary, content::BrowserContext::GetDefaultStoragePartition(context_) ->GetURLRequestContext(), this)); hunspell_dictionaries_.back()->AddObserver(this); hunspell_dictionaries_.back()->Load(); } } const std::vector>& SpellcheckService::GetHunspellDictionaries() { return hunspell_dictionaries_; } 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); auto process_hosts(content::RenderProcessHost::AllHostsIterator()); const std::vector additions(change.to_add().begin(), change.to_add().end()); const std::vector deletions(change.to_remove().begin(), change.to_remove().end()); while (!process_hosts.IsAtEnd()) { spellcheck::mojom::SpellCheckerPtr spellchecker; content::BindInterface(process_hosts.GetCurrentValue(), &spellchecker); spellchecker->CustomDictionaryChanged(additions, deletions); process_hosts.Advance(); } } 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 } // 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; } 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->GetHandle()) InitForRenderer(process); } } void SpellcheckService::OnSpellCheckDictionariesChanged() { // If there are hunspell dictionaries, then fire off notifications to the // renderers after the dictionaries are finished loading. LoadHunspellDictionaries(); // If there are no hunspell dictionaries to load, then immediately let the // renderers know the new state. if (hunspell_dictionaries_.empty()) InitForAllRenderers(); } void SpellcheckService::OnUseSpellingServiceChanged() { bool enabled = pref_change_registrar_.prefs()->GetBoolean( spellcheck::prefs::kSpellCheckUseSpellingService); if (metrics_) metrics_->RecordSpellingServiceStats(enabled); } void SpellcheckService::OnAcceptLanguagesChanged() { PrefService* prefs = user_prefs::UserPrefs::Get(context_); std::vector accept_languages = base::SplitString(prefs->GetString(prefs::kAcceptLanguages), ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); std::transform(accept_languages.begin(), accept_languages.end(), accept_languages.begin(), &spellcheck::GetCorrespondingSpellCheckLanguage); StringListPrefMember dictionaries_pref; dictionaries_pref.Init(spellcheck::prefs::kSpellCheckDictionaries, prefs); std::vector dictionaries = dictionaries_pref.GetValue(); std::vector filtered_dictionaries; for (const auto& dictionary : dictionaries) { if (std::find(accept_languages.begin(), accept_languages.end(), dictionary) != accept_languages.end()) { filtered_dictionaries.push_back(dictionary); } } dictionaries_pref.SetValue(filtered_dictionaries); }