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_message_filter.h"
6
7#include <algorithm>
8#include <functional>
9
10#include "base/bind.h"
11#include "base/prefs/pref_service.h"
12#include "base/strings/utf_string_conversions.h"
13#include "chrome/browser/spellchecker/spellcheck_factory.h"
14#include "chrome/browser/spellchecker/spellcheck_host_metrics.h"
15#include "chrome/browser/spellchecker/spellcheck_service.h"
16#include "chrome/browser/spellchecker/spelling_service_client.h"
17#include "chrome/common/pref_names.h"
18#include "chrome/common/spellcheck_marker.h"
19#include "chrome/common/spellcheck_messages.h"
20#include "content/public/browser/render_process_host.h"
21#include "net/url_request/url_fetcher.h"
22
23using content::BrowserThread;
24
25SpellCheckMessageFilter::SpellCheckMessageFilter(int render_process_id)
26    : BrowserMessageFilter(SpellCheckMsgStart),
27      render_process_id_(render_process_id),
28      client_(new SpellingServiceClient) {
29}
30
31void SpellCheckMessageFilter::OverrideThreadForMessage(
32    const IPC::Message& message, BrowserThread::ID* thread) {
33  // IPC messages arrive on IO thread, but spellcheck data lives on UI thread.
34  // The message filter overrides the thread for these messages because they
35  // access spellcheck data.
36  if (message.type() == SpellCheckHostMsg_RequestDictionary::ID ||
37      message.type() == SpellCheckHostMsg_NotifyChecked::ID ||
38      message.type() == SpellCheckHostMsg_RespondDocumentMarkers::ID)
39    *thread = BrowserThread::UI;
40#if !defined(OS_MACOSX)
41  if (message.type() == SpellCheckHostMsg_CallSpellingService::ID)
42    *thread = BrowserThread::UI;
43#endif
44}
45
46bool SpellCheckMessageFilter::OnMessageReceived(const IPC::Message& message) {
47  bool handled = true;
48  IPC_BEGIN_MESSAGE_MAP(SpellCheckMessageFilter, message)
49    IPC_MESSAGE_HANDLER(SpellCheckHostMsg_RequestDictionary,
50                        OnSpellCheckerRequestDictionary)
51    IPC_MESSAGE_HANDLER(SpellCheckHostMsg_NotifyChecked,
52                        OnNotifyChecked)
53    IPC_MESSAGE_HANDLER(SpellCheckHostMsg_RespondDocumentMarkers,
54                        OnRespondDocumentMarkers)
55#if !defined(OS_MACOSX)
56    IPC_MESSAGE_HANDLER(SpellCheckHostMsg_CallSpellingService,
57                        OnCallSpellingService)
58#endif
59    IPC_MESSAGE_UNHANDLED(handled = false)
60  IPC_END_MESSAGE_MAP()
61  return handled;
62}
63
64SpellCheckMessageFilter::~SpellCheckMessageFilter() {}
65
66void SpellCheckMessageFilter::OnSpellCheckerRequestDictionary() {
67  content::RenderProcessHost* host =
68      content::RenderProcessHost::FromID(render_process_id_);
69  if (!host)
70    return;  // Teardown.
71  // The renderer has requested that we initialize its spellchecker. This should
72  // generally only be called once per session, as after the first call, all
73  // future renderers will be passed the initialization information on startup
74  // (or when the dictionary changes in some way).
75  SpellcheckService* spellcheck_service =
76      SpellcheckServiceFactory::GetForContext(host->GetBrowserContext());
77
78  DCHECK(spellcheck_service);
79  // The spellchecker initialization already started and finished; just send
80  // it to the renderer.
81  spellcheck_service->InitForRenderer(host);
82
83  // TODO(rlp): Ensure that we do not initialize the hunspell dictionary more
84  // than once if we get requests from different renderers.
85}
86
87void SpellCheckMessageFilter::OnNotifyChecked(const base::string16& word,
88                                              bool misspelled) {
89  SpellcheckService* spellcheck = GetSpellcheckService();
90  // Spellcheck service may not be available for a renderer process that is
91  // shutting down.
92  if (!spellcheck)
93    return;
94  if (spellcheck->GetMetrics())
95    spellcheck->GetMetrics()->RecordCheckedWordStats(word, misspelled);
96}
97
98void SpellCheckMessageFilter::OnRespondDocumentMarkers(
99    const std::vector<uint32>& markers) {
100  SpellcheckService* spellcheck = GetSpellcheckService();
101  // Spellcheck service may not be available for a renderer process that is
102  // shutting down.
103  if (!spellcheck)
104    return;
105  spellcheck->GetFeedbackSender()->OnReceiveDocumentMarkers(
106      render_process_id_, markers);
107}
108
109#if !defined(OS_MACOSX)
110void SpellCheckMessageFilter::OnCallSpellingService(
111    int route_id,
112    int identifier,
113    const base::string16& text,
114    std::vector<SpellCheckMarker> markers) {
115  DCHECK(!text.empty());
116  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
117  // Erase invalid markers (with offsets out of boundaries of text length).
118  markers.erase(
119      std::remove_if(
120          markers.begin(),
121          markers.end(),
122          std::not1(SpellCheckMarker::IsValidPredicate(text.length()))),
123      markers.end());
124  CallSpellingService(text, route_id, identifier, markers);
125}
126
127void SpellCheckMessageFilter::OnTextCheckComplete(
128    int route_id,
129    int identifier,
130    const std::vector<SpellCheckMarker>& markers,
131    bool success,
132    const base::string16& text,
133    const std::vector<SpellCheckResult>& results) {
134  SpellcheckService* spellcheck = GetSpellcheckService();
135  // Spellcheck service may not be available for a renderer process that is
136  // shutting down.
137  if (!spellcheck)
138    return;
139  std::vector<SpellCheckResult> results_copy = results;
140  spellcheck->GetFeedbackSender()->OnSpellcheckResults(
141      render_process_id_, text, markers, &results_copy);
142
143  // Erase custom dictionary words from the spellcheck results and record
144  // in-dictionary feedback.
145  std::vector<SpellCheckResult>::iterator write_iter;
146  std::vector<SpellCheckResult>::iterator iter;
147  std::string text_copy = base::UTF16ToUTF8(text);
148  for (iter = write_iter = results_copy.begin();
149       iter != results_copy.end();
150       ++iter) {
151    if (spellcheck->GetCustomDictionary()->HasWord(
152            text_copy.substr(iter->location, iter->length))) {
153      spellcheck->GetFeedbackSender()->RecordInDictionary(iter->hash);
154    } else {
155      if (write_iter != iter)
156        *write_iter = *iter;
157      ++write_iter;
158    }
159  }
160  results_copy.erase(write_iter, results_copy.end());
161
162  Send(new SpellCheckMsg_RespondSpellingService(
163      route_id, identifier, success, text, results_copy));
164}
165
166// CallSpellingService always executes the callback OnTextCheckComplete.
167// (Which, in turn, sends a SpellCheckMsg_RespondSpellingService)
168void SpellCheckMessageFilter::CallSpellingService(
169    const base::string16& text,
170    int route_id,
171    int identifier,
172    const std::vector<SpellCheckMarker>& markers) {
173  content::RenderProcessHost* host =
174      content::RenderProcessHost::FromID(render_process_id_);
175
176  client_->RequestTextCheck(
177    host ? host->GetBrowserContext() : NULL,
178    SpellingServiceClient::SPELLCHECK,
179    text,
180    base::Bind(&SpellCheckMessageFilter::OnTextCheckComplete,
181               base::Unretained(this),
182               route_id,
183               identifier,
184               markers));
185}
186#endif
187
188SpellcheckService* SpellCheckMessageFilter::GetSpellcheckService() const {
189  return SpellcheckServiceFactory::GetForRenderProcessId(render_process_id_);
190}
191