1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/spellchecker/spellcheck_service.h"
6
7#include "base/prefs/pref_member.h"
8#include "base/prefs/pref_service.h"
9#include "base/strings/string_split.h"
10#include "base/synchronization/waitable_event.h"
11#include "chrome/browser/spellchecker/spellcheck_factory.h"
12#include "chrome/browser/spellchecker/spellcheck_host_metrics.h"
13#include "chrome/browser/spellchecker/spellcheck_hunspell_dictionary.h"
14#include "chrome/browser/spellchecker/spellcheck_platform_mac.h"
15#include "chrome/browser/spellchecker/spelling_service_client.h"
16#include "chrome/common/pref_names.h"
17#include "chrome/common/spellcheck_messages.h"
18#include "components/user_prefs/user_prefs.h"
19#include "content/public/browser/browser_context.h"
20#include "content/public/browser/browser_thread.h"
21#include "content/public/browser/notification_service.h"
22#include "content/public/browser/notification_types.h"
23#include "content/public/browser/render_process_host.h"
24#include "ipc/ipc_platform_file.h"
25
26using content::BrowserThread;
27using chrome::spellcheck_common::WordList;
28
29// TODO(rlp): I do not like globals, but keeping these for now during
30// transition.
31// An event used by browser tests to receive status events from this class and
32// its derived classes.
33base::WaitableEvent* g_status_event = NULL;
34SpellcheckService::EventType g_status_type =
35    SpellcheckService::BDICT_NOTINITIALIZED;
36
37SpellcheckService::SpellcheckService(content::BrowserContext* context)
38    : context_(context),
39      weak_ptr_factory_(this) {
40  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
41  PrefService* prefs = user_prefs::UserPrefs::Get(context);
42  pref_change_registrar_.Init(prefs);
43
44  std::string language_code;
45  std::string country_code;
46  chrome::spellcheck_common::GetISOLanguageCountryCodeFromLocale(
47      prefs->GetString(prefs::kSpellCheckDictionary),
48      &language_code,
49      &country_code);
50  feedback_sender_.reset(new spellcheck::FeedbackSender(
51      context->GetRequestContext(), language_code, country_code));
52
53  pref_change_registrar_.Add(
54      prefs::kEnableAutoSpellCorrect,
55      base::Bind(&SpellcheckService::OnEnableAutoSpellCorrectChanged,
56                 base::Unretained(this)));
57  pref_change_registrar_.Add(
58      prefs::kSpellCheckDictionary,
59      base::Bind(&SpellcheckService::OnSpellCheckDictionaryChanged,
60                 base::Unretained(this)));
61 pref_change_registrar_.Add(
62     prefs::kSpellCheckUseSpellingService,
63     base::Bind(&SpellcheckService::OnUseSpellingServiceChanged,
64                base::Unretained(this)));
65  pref_change_registrar_.Add(
66      prefs::kEnableContinuousSpellcheck,
67      base::Bind(&SpellcheckService::InitForAllRenderers,
68                 base::Unretained(this)));
69
70  OnSpellCheckDictionaryChanged();
71
72  custom_dictionary_.reset(new SpellcheckCustomDictionary(context_->GetPath()));
73  custom_dictionary_->AddObserver(this);
74  custom_dictionary_->Load();
75
76  registrar_.Add(this,
77                 content::NOTIFICATION_RENDERER_PROCESS_CREATED,
78                 content::NotificationService::AllSources());
79}
80
81SpellcheckService::~SpellcheckService() {
82  // Remove pref observers
83  pref_change_registrar_.RemoveAll();
84}
85
86// static
87int SpellcheckService::GetSpellCheckLanguages(
88    content::BrowserContext* context,
89    std::vector<std::string>* languages) {
90  PrefService* prefs = user_prefs::UserPrefs::Get(context);
91  StringPrefMember accept_languages_pref;
92  StringPrefMember dictionary_language_pref;
93  accept_languages_pref.Init(prefs::kAcceptLanguages, prefs);
94  dictionary_language_pref.Init(prefs::kSpellCheckDictionary, prefs);
95  std::string dictionary_language = dictionary_language_pref.GetValue();
96
97  // Now scan through the list of accept languages, and find possible mappings
98  // from this list to the existing list of spell check languages.
99  std::vector<std::string> accept_languages;
100
101#if defined(OS_MACOSX)
102  if (spellcheck_mac::SpellCheckerAvailable())
103    spellcheck_mac::GetAvailableLanguages(&accept_languages);
104  else
105    base::SplitString(accept_languages_pref.GetValue(), ',', &accept_languages);
106#else
107  base::SplitString(accept_languages_pref.GetValue(), ',', &accept_languages);
108#endif  // !OS_MACOSX
109
110  GetSpellCheckLanguagesFromAcceptLanguages(
111      accept_languages, dictionary_language, languages);
112
113  for (size_t i = 0; i < languages->size(); ++i) {
114    if ((*languages)[i] == dictionary_language)
115      return i;
116  }
117  return -1;
118}
119
120// static
121void SpellcheckService::GetSpellCheckLanguagesFromAcceptLanguages(
122    const std::vector<std::string>& accept_languages,
123    const std::string& dictionary_language,
124    std::vector<std::string>* languages) {
125  // The current dictionary language should be there.
126  languages->push_back(dictionary_language);
127
128  for (std::vector<std::string>::const_iterator i = accept_languages.begin();
129       i != accept_languages.end(); ++i) {
130    std::string language =
131        chrome::spellcheck_common::GetCorrespondingSpellCheckLanguage(*i);
132    if (!language.empty() &&
133        std::find(languages->begin(), languages->end(), language) ==
134        languages->end()) {
135      languages->push_back(language);
136    }
137  }
138}
139
140// static
141bool SpellcheckService::SignalStatusEvent(
142    SpellcheckService::EventType status_type) {
143  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
144
145  if (!g_status_event)
146    return false;
147  g_status_type = status_type;
148  g_status_event->Signal();
149  return true;
150}
151
152void SpellcheckService::StartRecordingMetrics(bool spellcheck_enabled) {
153  metrics_.reset(new SpellCheckHostMetrics());
154  metrics_->RecordEnabledStats(spellcheck_enabled);
155  OnUseSpellingServiceChanged();
156}
157
158void SpellcheckService::InitForRenderer(content::RenderProcessHost* process) {
159  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
160
161  content::BrowserContext* context = process->GetBrowserContext();
162  if (SpellcheckServiceFactory::GetForContext(context) != this)
163    return;
164
165  PrefService* prefs = user_prefs::UserPrefs::Get(context);
166  IPC::PlatformFileForTransit file = IPC::InvalidPlatformFileForTransit();
167
168  if (hunspell_dictionary_->GetDictionaryFile().IsValid()) {
169    file = IPC::GetFileHandleForProcess(
170        hunspell_dictionary_->GetDictionaryFile().GetPlatformFile(),
171        process->GetHandle(), false);
172  }
173
174  process->Send(new SpellCheckMsg_Init(
175      file,
176      custom_dictionary_->GetWords(),
177      hunspell_dictionary_->GetLanguage(),
178      prefs->GetBoolean(prefs::kEnableAutoSpellCorrect)));
179  process->Send(new SpellCheckMsg_EnableSpellCheck(
180      prefs->GetBoolean(prefs::kEnableContinuousSpellcheck)));
181}
182
183SpellCheckHostMetrics* SpellcheckService::GetMetrics() const {
184  return metrics_.get();
185}
186
187SpellcheckCustomDictionary* SpellcheckService::GetCustomDictionary() {
188  return custom_dictionary_.get();
189}
190
191SpellcheckHunspellDictionary* SpellcheckService::GetHunspellDictionary() {
192  return hunspell_dictionary_.get();
193}
194
195spellcheck::FeedbackSender* SpellcheckService::GetFeedbackSender() {
196  return feedback_sender_.get();
197}
198
199bool SpellcheckService::LoadExternalDictionary(std::string language,
200                                               std::string locale,
201                                               std::string path,
202                                               DictionaryFormat format) {
203  return false;
204}
205
206bool SpellcheckService::UnloadExternalDictionary(std::string path) {
207  return false;
208}
209
210void SpellcheckService::Observe(int type,
211                                const content::NotificationSource& source,
212                                const content::NotificationDetails& details) {
213  DCHECK(type == content::NOTIFICATION_RENDERER_PROCESS_CREATED);
214  content::RenderProcessHost* process =
215      content::Source<content::RenderProcessHost>(source).ptr();
216  InitForRenderer(process);
217}
218
219void SpellcheckService::OnCustomDictionaryLoaded() {
220  InitForAllRenderers();
221}
222
223void SpellcheckService::OnCustomDictionaryChanged(
224    const SpellcheckCustomDictionary::Change& dictionary_change) {
225  for (content::RenderProcessHost::iterator i(
226          content::RenderProcessHost::AllHostsIterator());
227       !i.IsAtEnd(); i.Advance()) {
228    i.GetCurrentValue()->Send(new SpellCheckMsg_CustomDictionaryChanged(
229        dictionary_change.to_add(),
230        dictionary_change.to_remove()));
231  }
232}
233
234void SpellcheckService::OnHunspellDictionaryInitialized() {
235  InitForAllRenderers();
236}
237
238void SpellcheckService::OnHunspellDictionaryDownloadBegin() {
239}
240
241void SpellcheckService::OnHunspellDictionaryDownloadSuccess() {
242}
243
244void SpellcheckService::OnHunspellDictionaryDownloadFailure() {
245}
246
247// static
248void SpellcheckService::AttachStatusEvent(base::WaitableEvent* status_event) {
249  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
250
251  g_status_event = status_event;
252}
253
254// static
255SpellcheckService::EventType SpellcheckService::GetStatusEvent() {
256  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
257  return g_status_type;
258}
259
260void SpellcheckService::InitForAllRenderers() {
261  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
262  for (content::RenderProcessHost::iterator i(
263          content::RenderProcessHost::AllHostsIterator());
264       !i.IsAtEnd(); i.Advance()) {
265    content::RenderProcessHost* process = i.GetCurrentValue();
266    if (process && process->GetHandle())
267      InitForRenderer(process);
268  }
269}
270
271void SpellcheckService::OnEnableAutoSpellCorrectChanged() {
272  bool enabled = pref_change_registrar_.prefs()->GetBoolean(
273      prefs::kEnableAutoSpellCorrect);
274  for (content::RenderProcessHost::iterator i(
275           content::RenderProcessHost::AllHostsIterator());
276       !i.IsAtEnd(); i.Advance()) {
277    content::RenderProcessHost* process = i.GetCurrentValue();
278    process->Send(new SpellCheckMsg_EnableAutoSpellCorrect(enabled));
279  }
280}
281
282void SpellcheckService::OnSpellCheckDictionaryChanged() {
283  if (hunspell_dictionary_.get())
284    hunspell_dictionary_->RemoveObserver(this);
285  PrefService* prefs = user_prefs::UserPrefs::Get(context_);
286  DCHECK(prefs);
287
288  std::string dictionary =
289      prefs->GetString(prefs::kSpellCheckDictionary);
290  hunspell_dictionary_.reset(new SpellcheckHunspellDictionary(
291      dictionary, context_->GetRequestContext(), this));
292  hunspell_dictionary_->AddObserver(this);
293  hunspell_dictionary_->Load();
294  std::string language_code;
295  std::string country_code;
296  chrome::spellcheck_common::GetISOLanguageCountryCodeFromLocale(
297      dictionary, &language_code, &country_code);
298  feedback_sender_->OnLanguageCountryChange(language_code, country_code);
299  UpdateFeedbackSenderState();
300}
301
302void SpellcheckService::OnUseSpellingServiceChanged() {
303  bool enabled = pref_change_registrar_.prefs()->GetBoolean(
304      prefs::kSpellCheckUseSpellingService);
305  if (metrics_)
306    metrics_->RecordSpellingServiceStats(enabled);
307  UpdateFeedbackSenderState();
308}
309
310void SpellcheckService::UpdateFeedbackSenderState() {
311  if (SpellingServiceClient::IsAvailable(
312          context_, SpellingServiceClient::SPELLCHECK)) {
313    feedback_sender_->StartFeedbackCollection();
314  } else {
315    feedback_sender_->StopFeedbackCollection();
316  }
317}
318