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_mac.h"
6
7#include <algorithm>
8#include <functional>
9
10#include "base/barrier_closure.h"
11#include "base/bind.h"
12#include "chrome/browser/spellchecker/spellcheck_factory.h"
13#include "chrome/browser/spellchecker/spellcheck_platform_mac.h"
14#include "chrome/browser/spellchecker/spellcheck_service.h"
15#include "chrome/browser/spellchecker/spelling_service_client.h"
16#include "chrome/common/spellcheck_messages.h"
17#include "chrome/common/spellcheck_result.h"
18#include "content/public/browser/browser_context.h"
19#include "content/public/browser/render_process_host.h"
20
21using content::BrowserThread;
22using content::BrowserContext;
23
24namespace {
25
26bool CompareLocation(const SpellCheckResult& r1,
27                     const SpellCheckResult& r2) {
28  return r1.location < r2.location;
29}
30
31}  // namespace
32
33class SpellingRequest {
34 public:
35  SpellingRequest(SpellingServiceClient* client,
36                  content::BrowserMessageFilter* destination,
37                  int render_process_id);
38
39  void RequestCheck(const base::string16& text,
40                    int route_id,
41                    int identifier,
42                    int document_tag,
43                    const std::vector<SpellCheckMarker>& markers);
44 private:
45  // Request server-side checking.
46  void RequestRemoteCheck(const base::string16& text);
47
48  // Request a check from local spell checker.
49  void RequestLocalCheck(const base::string16& text, int document_tag);
50
51  // Check if all pending requests are done, send reply to render process if so.
52  void OnCheckCompleted();
53
54  // Called when server-side checking is complete.
55  void OnRemoteCheckCompleted(bool success,
56                              const base::string16& text,
57                              const std::vector<SpellCheckResult>& results);
58
59  // Called when local checking is complete.
60  void OnLocalCheckCompleted(const std::vector<SpellCheckResult>& results);
61
62  std::vector<SpellCheckResult> local_results_;
63  std::vector<SpellCheckResult> remote_results_;
64
65  // Barrier closure for completion of both remote and local check.
66  base::Closure completion_barrier_;
67  bool remote_success_;
68
69  SpellingServiceClient* client_;  // Owned by |destination|.
70  content::BrowserMessageFilter* destination_;  // ref-counted.
71  int render_process_id_;
72
73  int route_id_;
74  int identifier_;
75  int document_tag_;
76  std::vector<SpellCheckMarker> markers_;
77};
78
79SpellingRequest::SpellingRequest(SpellingServiceClient* client,
80                                 content::BrowserMessageFilter* destination,
81                                 int render_process_id)
82    : remote_success_(false),
83      client_(client),
84      destination_(destination),
85      render_process_id_(render_process_id),
86      route_id_(-1),
87      identifier_(-1),
88      document_tag_(-1) {
89  destination_->AddRef();
90}
91
92void SpellingRequest::RequestCheck(
93    const base::string16& text,
94    int route_id,
95    int identifier,
96    int document_tag,
97    const std::vector<SpellCheckMarker>& markers) {
98  DCHECK(!text.empty());
99  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
100
101  route_id_ = route_id;
102  identifier_ = identifier;
103  document_tag_ = document_tag;
104  markers_ = markers;
105
106  // Send the remote query out. The barrier owns |this|, ensuring it is deleted
107  // after completion.
108  completion_barrier_ =
109      BarrierClosure(2,
110                     base::Bind(&SpellingRequest::OnCheckCompleted,
111                     base::Owned(this)));
112  RequestRemoteCheck(text);
113  RequestLocalCheck(text, document_tag_);
114}
115
116void SpellingRequest::RequestRemoteCheck(const base::string16& text) {
117  BrowserContext* context = NULL;
118  content::RenderProcessHost* host =
119      content::RenderProcessHost::FromID(render_process_id_);
120  if (host)
121    context = host->GetBrowserContext();
122
123  client_->RequestTextCheck(
124    context,
125    SpellingServiceClient::SPELLCHECK,
126    text,
127    base::Bind(&SpellingRequest::OnRemoteCheckCompleted,
128               base::Unretained(this)));
129}
130
131void SpellingRequest::RequestLocalCheck(const base::string16& text,
132                                        int document_tag) {
133  spellcheck_mac::RequestTextCheck(
134      document_tag,
135      text,
136      base::Bind(&SpellingRequest::OnLocalCheckCompleted,
137                 base::Unretained(this)));
138}
139
140void SpellingRequest::OnCheckCompleted() {
141  // Final completion can happen on any thread - don't DCHECK thread.
142  const std::vector<SpellCheckResult>* check_results = &local_results_;
143  if (remote_success_) {
144    std::sort(remote_results_.begin(), remote_results_.end(), CompareLocation);
145    std::sort(local_results_.begin(), local_results_.end(), CompareLocation);
146    SpellCheckMessageFilterMac::CombineResults(&remote_results_,
147                                               local_results_);
148    check_results = &remote_results_;
149  }
150
151  destination_->Send(
152      new SpellCheckMsg_RespondTextCheck(
153          route_id_,
154          identifier_,
155          *check_results));
156  destination_->Release();
157
158  // Object is self-managed - at this point, its life span is over.
159  // No need to delete, since the OnCheckCompleted callback owns |this|.
160}
161
162void SpellingRequest::OnRemoteCheckCompleted(
163    bool success,
164    const base::string16& text,
165    const std::vector<SpellCheckResult>& results) {
166  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
167  remote_success_ = success;
168  remote_results_ = results;
169
170  SpellcheckService* spellcheck_service =
171      SpellcheckServiceFactory::GetForRenderProcessId(render_process_id_);
172  if (spellcheck_service) {
173    spellcheck_service->GetFeedbackSender()->OnSpellcheckResults(
174        render_process_id_,
175        text,
176        markers_,
177        &remote_results_);
178  }
179
180  completion_barrier_.Run();
181}
182
183void SpellingRequest::OnLocalCheckCompleted(
184    const std::vector<SpellCheckResult>& results) {
185  // Local checking can happen on any thread - don't DCHECK thread.
186  local_results_ = results;
187  completion_barrier_.Run();
188}
189
190
191SpellCheckMessageFilterMac::SpellCheckMessageFilterMac(int render_process_id)
192    : BrowserMessageFilter(SpellCheckMsgStart),
193      render_process_id_(render_process_id),
194      client_(new SpellingServiceClient) {
195}
196
197void SpellCheckMessageFilterMac::OverrideThreadForMessage(
198    const IPC::Message& message, BrowserThread::ID* thread) {
199  if (message.type() == SpellCheckHostMsg_RequestTextCheck::ID)
200    *thread = BrowserThread::UI;
201}
202
203bool SpellCheckMessageFilterMac::OnMessageReceived(
204    const IPC::Message& message) {
205  bool handled = true;
206  IPC_BEGIN_MESSAGE_MAP(SpellCheckMessageFilterMac, message)
207    IPC_MESSAGE_HANDLER(SpellCheckHostMsg_CheckSpelling,
208                        OnCheckSpelling)
209    IPC_MESSAGE_HANDLER(SpellCheckHostMsg_FillSuggestionList,
210                        OnFillSuggestionList)
211    IPC_MESSAGE_HANDLER(SpellCheckHostMsg_ShowSpellingPanel,
212                        OnShowSpellingPanel)
213    IPC_MESSAGE_HANDLER(SpellCheckHostMsg_UpdateSpellingPanelWithMisspelledWord,
214                        OnUpdateSpellingPanelWithMisspelledWord)
215    IPC_MESSAGE_HANDLER(SpellCheckHostMsg_RequestTextCheck,
216                        OnRequestTextCheck)
217    IPC_MESSAGE_UNHANDLED(handled = false)
218  IPC_END_MESSAGE_MAP()
219  return handled;
220}
221
222// static
223void SpellCheckMessageFilterMac::CombineResults(
224    std::vector<SpellCheckResult>* remote_results,
225    const std::vector<SpellCheckResult>& local_results) {
226  std::vector<SpellCheckResult>::const_iterator local_iter(
227      local_results.begin());
228  std::vector<SpellCheckResult>::iterator remote_iter;
229
230  for (remote_iter = remote_results->begin();
231       remote_iter != remote_results->end();
232       ++remote_iter) {
233    // Discard all local results occurring before remote result.
234    while (local_iter != local_results.end() &&
235           local_iter->location < remote_iter->location) {
236      local_iter++;
237    }
238
239    // Unless local and remote result coincide, result is GRAMMAR.
240    remote_iter->decoration = SpellCheckResult::GRAMMAR;
241    if (local_iter != local_results.end() &&
242        local_iter->location == remote_iter->location &&
243        local_iter->length == remote_iter->length) {
244      remote_iter->decoration = SpellCheckResult::SPELLING;
245    }
246  }
247}
248
249SpellCheckMessageFilterMac::~SpellCheckMessageFilterMac() {}
250
251void SpellCheckMessageFilterMac::OnCheckSpelling(const base::string16& word,
252                                                 int route_id,
253                                                 bool* correct) {
254  *correct = spellcheck_mac::CheckSpelling(word, ToDocumentTag(route_id));
255}
256
257void SpellCheckMessageFilterMac::OnFillSuggestionList(
258    const base::string16& word,
259    std::vector<base::string16>* suggestions) {
260  spellcheck_mac::FillSuggestionList(word, suggestions);
261}
262
263void SpellCheckMessageFilterMac::OnShowSpellingPanel(bool show) {
264  spellcheck_mac::ShowSpellingPanel(show);
265}
266
267void SpellCheckMessageFilterMac::OnUpdateSpellingPanelWithMisspelledWord(
268    const base::string16& word) {
269  spellcheck_mac::UpdateSpellingPanelWithMisspelledWord(word);
270}
271
272void SpellCheckMessageFilterMac::OnRequestTextCheck(
273    int route_id,
274    int identifier,
275    const base::string16& text,
276    std::vector<SpellCheckMarker> markers) {
277  DCHECK(!text.empty());
278  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
279  // Erase invalid markers (with offsets out of boundaries of text length).
280  markers.erase(
281      std::remove_if(
282          markers.begin(),
283          markers.end(),
284          std::not1(SpellCheckMarker::IsValidPredicate(text.length()))),
285      markers.end());
286  // SpellingRequest self-destructs.
287  SpellingRequest* request =
288    new SpellingRequest(client_.get(), this, render_process_id_);
289  request->RequestCheck(
290      text, route_id, identifier, ToDocumentTag(route_id), markers);
291}
292
293int SpellCheckMessageFilterMac::ToDocumentTag(int route_id) {
294  if (!tag_map_.count(route_id))
295    tag_map_[route_id] = spellcheck_mac::GetDocumentTag();
296  return tag_map_[route_id];
297}
298
299// TODO(groby): We are currently not notified of retired tags. We need
300// to track destruction of RenderViewHosts on the browser process side
301// to update our mappings when a document goes away.
302void SpellCheckMessageFilterMac::RetireDocumentTag(int route_id) {
303  spellcheck_mac::CloseDocumentWithTag(ToDocumentTag(route_id));
304  tag_map_.erase(route_id);
305}
306
307