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/renderer/spellchecker/spellcheck.h"
6
7 #include "base/bind.h"
8 #include "base/message_loop/message_loop_proxy.h"
9 #include "base/strings/utf_string_conversions.h"
10 #include "chrome/common/render_messages.h"
11 #include "chrome/common/spellcheck_common.h"
12 #include "chrome/common/spellcheck_messages.h"
13 #include "chrome/common/spellcheck_result.h"
14 #include "chrome/renderer/spellchecker/spellcheck_language.h"
15 #include "chrome/renderer/spellchecker/spellcheck_provider.h"
16 #include "content/public/renderer/render_thread.h"
17 #include "content/public/renderer/render_view.h"
18 #include "content/public/renderer/render_view_visitor.h"
19 #include "third_party/WebKit/public/web/WebTextCheckingCompletion.h"
20 #include "third_party/WebKit/public/web/WebTextCheckingResult.h"
21 #include "third_party/WebKit/public/web/WebTextDecorationType.h"
22 #include "third_party/WebKit/public/web/WebView.h"
23
24 using blink::WebVector;
25 using blink::WebTextCheckingResult;
26 using blink::WebTextDecorationType;
27
28 namespace {
29
30 class UpdateSpellcheckEnabled : public content::RenderViewVisitor {
31 public:
UpdateSpellcheckEnabled(bool enabled)32 explicit UpdateSpellcheckEnabled(bool enabled) : enabled_(enabled) {}
33 virtual bool Visit(content::RenderView* render_view) OVERRIDE;
34
35 private:
36 bool enabled_; // New spellcheck-enabled state.
37 DISALLOW_COPY_AND_ASSIGN(UpdateSpellcheckEnabled);
38 };
39
Visit(content::RenderView * render_view)40 bool UpdateSpellcheckEnabled::Visit(content::RenderView* render_view) {
41 SpellCheckProvider* provider = SpellCheckProvider::Get(render_view);
42 DCHECK(provider);
43 provider->EnableSpellcheck(enabled_);
44 return true;
45 }
46
47 class DocumentMarkersCollector : public content::RenderViewVisitor {
48 public:
DocumentMarkersCollector()49 DocumentMarkersCollector() {}
~DocumentMarkersCollector()50 virtual ~DocumentMarkersCollector() {}
markers() const51 const std::vector<uint32>& markers() const { return markers_; }
52 virtual bool Visit(content::RenderView* render_view) OVERRIDE;
53
54 private:
55 std::vector<uint32> markers_;
56 DISALLOW_COPY_AND_ASSIGN(DocumentMarkersCollector);
57 };
58
Visit(content::RenderView * render_view)59 bool DocumentMarkersCollector::Visit(content::RenderView* render_view) {
60 if (!render_view || !render_view->GetWebView())
61 return true;
62 WebVector<uint32> markers;
63 render_view->GetWebView()->spellingMarkers(&markers);
64 for (size_t i = 0; i < markers.size(); ++i)
65 markers_.push_back(markers[i]);
66 // Visit all render views.
67 return true;
68 }
69
70 } // namespace
71
72 class SpellCheck::SpellcheckRequest {
73 public:
SpellcheckRequest(const base::string16 & text,blink::WebTextCheckingCompletion * completion)74 SpellcheckRequest(const base::string16& text,
75 blink::WebTextCheckingCompletion* completion)
76 : text_(text), completion_(completion) {
77 DCHECK(completion);
78 }
~SpellcheckRequest()79 ~SpellcheckRequest() {}
80
text()81 base::string16 text() { return text_; }
completion()82 blink::WebTextCheckingCompletion* completion() { return completion_; }
83
84 private:
85 base::string16 text_; // Text to be checked in this task.
86
87 // The interface to send the misspelled ranges to WebKit.
88 blink::WebTextCheckingCompletion* completion_;
89
90 DISALLOW_COPY_AND_ASSIGN(SpellcheckRequest);
91 };
92
93
94 // Initializes SpellCheck object.
95 // spellcheck_enabled_ currently MUST be set to true, due to peculiarities of
96 // the initialization sequence.
97 // Since it defaults to true, newly created SpellCheckProviders will enable
98 // spellchecking. After the first word is typed, the provider requests a check,
99 // which in turn triggers the delayed initialization sequence in SpellCheck.
100 // This does send a message to the browser side, which triggers the creation
101 // of the SpellcheckService. That does create the observer for the preference
102 // responsible for enabling/disabling checking, which allows subsequent changes
103 // to that preference to be sent to all SpellCheckProviders.
104 // Setting |spellcheck_enabled_| to false by default prevents that mechanism,
105 // and as such the SpellCheckProviders will never be notified of different
106 // values.
107 // TODO(groby): Simplify this.
SpellCheck()108 SpellCheck::SpellCheck()
109 : auto_spell_correct_turned_on_(false),
110 spellcheck_enabled_(true) {
111 }
112
~SpellCheck()113 SpellCheck::~SpellCheck() {
114 }
115
OnControlMessageReceived(const IPC::Message & message)116 bool SpellCheck::OnControlMessageReceived(const IPC::Message& message) {
117 bool handled = true;
118 IPC_BEGIN_MESSAGE_MAP(SpellCheck, message)
119 IPC_MESSAGE_HANDLER(SpellCheckMsg_Init, OnInit)
120 IPC_MESSAGE_HANDLER(SpellCheckMsg_CustomDictionaryChanged,
121 OnCustomDictionaryChanged)
122 IPC_MESSAGE_HANDLER(SpellCheckMsg_EnableAutoSpellCorrect,
123 OnEnableAutoSpellCorrect)
124 IPC_MESSAGE_HANDLER(SpellCheckMsg_EnableSpellCheck, OnEnableSpellCheck)
125 IPC_MESSAGE_HANDLER(SpellCheckMsg_RequestDocumentMarkers,
126 OnRequestDocumentMarkers)
127 IPC_MESSAGE_UNHANDLED(handled = false)
128 IPC_END_MESSAGE_MAP()
129
130 return handled;
131 }
132
OnInit(IPC::PlatformFileForTransit bdict_file,const std::set<std::string> & custom_words,const std::string & language,bool auto_spell_correct)133 void SpellCheck::OnInit(IPC::PlatformFileForTransit bdict_file,
134 const std::set<std::string>& custom_words,
135 const std::string& language,
136 bool auto_spell_correct) {
137 Init(IPC::PlatformFileForTransitToFile(bdict_file),
138 custom_words, language);
139 auto_spell_correct_turned_on_ = auto_spell_correct;
140 #if !defined(OS_MACOSX)
141 PostDelayedSpellCheckTask(pending_request_param_.release());
142 #endif
143 }
144
OnCustomDictionaryChanged(const std::vector<std::string> & words_added,const std::vector<std::string> & words_removed)145 void SpellCheck::OnCustomDictionaryChanged(
146 const std::vector<std::string>& words_added,
147 const std::vector<std::string>& words_removed) {
148 custom_dictionary_.OnCustomDictionaryChanged(words_added, words_removed);
149 }
150
OnEnableAutoSpellCorrect(bool enable)151 void SpellCheck::OnEnableAutoSpellCorrect(bool enable) {
152 auto_spell_correct_turned_on_ = enable;
153 }
154
OnEnableSpellCheck(bool enable)155 void SpellCheck::OnEnableSpellCheck(bool enable) {
156 spellcheck_enabled_ = enable;
157 UpdateSpellcheckEnabled updater(enable);
158 content::RenderView::ForEach(&updater);
159 }
160
OnRequestDocumentMarkers()161 void SpellCheck::OnRequestDocumentMarkers() {
162 DocumentMarkersCollector collector;
163 content::RenderView::ForEach(&collector);
164 content::RenderThread::Get()->Send(
165 new SpellCheckHostMsg_RespondDocumentMarkers(collector.markers()));
166 }
167
168 // TODO(groby): Make sure we always have a spelling engine, even before Init()
169 // is called.
Init(base::File file,const std::set<std::string> & custom_words,const std::string & language)170 void SpellCheck::Init(base::File file,
171 const std::set<std::string>& custom_words,
172 const std::string& language) {
173 spellcheck_.Init(file.Pass(), language);
174 custom_dictionary_.Init(custom_words);
175 }
176
SpellCheckWord(const base::char16 * in_word,int in_word_len,int tag,int * misspelling_start,int * misspelling_len,std::vector<base::string16> * optional_suggestions)177 bool SpellCheck::SpellCheckWord(
178 const base::char16* in_word,
179 int in_word_len,
180 int tag,
181 int* misspelling_start,
182 int* misspelling_len,
183 std::vector<base::string16>* optional_suggestions) {
184 DCHECK(in_word_len >= 0);
185 DCHECK(misspelling_start && misspelling_len) << "Out vars must be given.";
186
187 // Do nothing if we need to delay initialization. (Rather than blocking,
188 // report the word as correctly spelled.)
189 if (InitializeIfNeeded())
190 return true;
191
192 return spellcheck_.SpellCheckWord(in_word, in_word_len,
193 tag,
194 misspelling_start, misspelling_len,
195 optional_suggestions);
196 }
197
SpellCheckParagraph(const base::string16 & text,WebVector<WebTextCheckingResult> * results)198 bool SpellCheck::SpellCheckParagraph(
199 const base::string16& text,
200 WebVector<WebTextCheckingResult>* results) {
201 #if !defined(OS_MACOSX)
202 // Mac has its own spell checker, so this method will not be used.
203 DCHECK(results);
204 std::vector<WebTextCheckingResult> textcheck_results;
205 size_t length = text.length();
206 size_t offset = 0;
207
208 // Spellcheck::SpellCheckWord() automatically breaks text into words and
209 // checks the spellings of the extracted words. This function sets the
210 // position and length of the first misspelled word and returns false when
211 // the text includes misspelled words. Therefore, we just repeat calling the
212 // function until it returns true to check the whole text.
213 int misspelling_start = 0;
214 int misspelling_length = 0;
215 while (offset <= length) {
216 if (SpellCheckWord(&text[offset],
217 length - offset,
218 0,
219 &misspelling_start,
220 &misspelling_length,
221 NULL)) {
222 results->assign(textcheck_results);
223 return true;
224 }
225
226 if (!custom_dictionary_.SpellCheckWord(
227 text, misspelling_start + offset, misspelling_length)) {
228 base::string16 replacement;
229 textcheck_results.push_back(WebTextCheckingResult(
230 blink::WebTextDecorationTypeSpelling,
231 misspelling_start + offset,
232 misspelling_length,
233 replacement));
234 }
235 offset += misspelling_start + misspelling_length;
236 }
237 results->assign(textcheck_results);
238 return false;
239 #else
240 // This function is only invoked for spell checker functionality that runs
241 // on the render thread. OSX builds don't have that.
242 NOTREACHED();
243 return true;
244 #endif
245 }
246
GetAutoCorrectionWord(const base::string16 & word,int tag)247 base::string16 SpellCheck::GetAutoCorrectionWord(const base::string16& word,
248 int tag) {
249 base::string16 autocorrect_word;
250 if (!auto_spell_correct_turned_on_)
251 return autocorrect_word; // Return the empty string.
252
253 int word_length = static_cast<int>(word.size());
254 if (word_length < 2 ||
255 word_length > chrome::spellcheck_common::kMaxAutoCorrectWordSize)
256 return autocorrect_word;
257
258 if (InitializeIfNeeded())
259 return autocorrect_word;
260
261 base::char16 misspelled_word[
262 chrome::spellcheck_common::kMaxAutoCorrectWordSize + 1];
263 const base::char16* word_char = word.c_str();
264 for (int i = 0; i <= chrome::spellcheck_common::kMaxAutoCorrectWordSize;
265 ++i) {
266 if (i >= word_length)
267 misspelled_word[i] = 0;
268 else
269 misspelled_word[i] = word_char[i];
270 }
271
272 // Swap adjacent characters and spellcheck.
273 int misspelling_start, misspelling_len;
274 for (int i = 0; i < word_length - 1; i++) {
275 // Swap.
276 std::swap(misspelled_word[i], misspelled_word[i + 1]);
277
278 // Check spelling.
279 misspelling_start = misspelling_len = 0;
280 SpellCheckWord(misspelled_word, word_length, tag, &misspelling_start,
281 &misspelling_len, NULL);
282
283 // Make decision: if only one swap produced a valid word, then we want to
284 // return it. If we found two or more, we don't do autocorrection.
285 if (misspelling_len == 0) {
286 if (autocorrect_word.empty()) {
287 autocorrect_word.assign(misspelled_word);
288 } else {
289 autocorrect_word.clear();
290 break;
291 }
292 }
293
294 // Restore the swapped characters.
295 std::swap(misspelled_word[i], misspelled_word[i + 1]);
296 }
297 return autocorrect_word;
298 }
299
300 #if !defined(OS_MACOSX) // OSX uses its own spell checker
RequestTextChecking(const base::string16 & text,blink::WebTextCheckingCompletion * completion)301 void SpellCheck::RequestTextChecking(
302 const base::string16& text,
303 blink::WebTextCheckingCompletion* completion) {
304 // Clean up the previous request before starting a new request.
305 if (pending_request_param_.get())
306 pending_request_param_->completion()->didCancelCheckingText();
307
308 pending_request_param_.reset(new SpellcheckRequest(
309 text, completion));
310 // We will check this text after we finish loading the hunspell dictionary.
311 if (InitializeIfNeeded())
312 return;
313
314 PostDelayedSpellCheckTask(pending_request_param_.release());
315 }
316 #endif
317
InitializeIfNeeded()318 bool SpellCheck::InitializeIfNeeded() {
319 return spellcheck_.InitializeIfNeeded();
320 }
321
322 #if !defined(OS_MACOSX) // OSX doesn't have |pending_request_param_|
PostDelayedSpellCheckTask(SpellcheckRequest * request)323 void SpellCheck::PostDelayedSpellCheckTask(SpellcheckRequest* request) {
324 if (!request)
325 return;
326
327 base::MessageLoopProxy::current()->PostTask(FROM_HERE,
328 base::Bind(&SpellCheck::PerformSpellCheck,
329 AsWeakPtr(),
330 base::Owned(request)));
331 }
332 #endif
333
334 #if !defined(OS_MACOSX) // Mac uses its native engine instead.
PerformSpellCheck(SpellcheckRequest * param)335 void SpellCheck::PerformSpellCheck(SpellcheckRequest* param) {
336 DCHECK(param);
337
338 if (!spellcheck_.IsEnabled()) {
339 param->completion()->didCancelCheckingText();
340 } else {
341 WebVector<blink::WebTextCheckingResult> results;
342 SpellCheckParagraph(param->text(), &results);
343 param->completion()->didFinishCheckingText(results);
344 }
345 }
346 #endif
347
CreateTextCheckingResults(ResultFilter filter,int line_offset,const base::string16 & line_text,const std::vector<SpellCheckResult> & spellcheck_results,WebVector<WebTextCheckingResult> * textcheck_results)348 void SpellCheck::CreateTextCheckingResults(
349 ResultFilter filter,
350 int line_offset,
351 const base::string16& line_text,
352 const std::vector<SpellCheckResult>& spellcheck_results,
353 WebVector<WebTextCheckingResult>* textcheck_results) {
354 // Double-check misspelled words with our spellchecker and attach grammar
355 // markers to them if our spellchecker tells they are correct words, i.e. they
356 // are probably contextually-misspelled words.
357 const base::char16* text = line_text.c_str();
358 std::vector<WebTextCheckingResult> list;
359 for (size_t i = 0; i < spellcheck_results.size(); ++i) {
360 SpellCheckResult::Decoration decoration = spellcheck_results[i].decoration;
361 int word_location = spellcheck_results[i].location;
362 int word_length = spellcheck_results[i].length;
363 int misspelling_start = 0;
364 int misspelling_length = 0;
365 if (decoration == SpellCheckResult::SPELLING &&
366 filter == USE_NATIVE_CHECKER) {
367 if (SpellCheckWord(text + word_location, word_length, 0,
368 &misspelling_start, &misspelling_length, NULL)) {
369 decoration = SpellCheckResult::GRAMMAR;
370 }
371 }
372 if (!custom_dictionary_.SpellCheckWord(
373 line_text, word_location, word_length)) {
374 list.push_back(WebTextCheckingResult(
375 static_cast<WebTextDecorationType>(decoration),
376 word_location + line_offset,
377 word_length,
378 spellcheck_results[i].replacement,
379 spellcheck_results[i].hash));
380 }
381 }
382 textcheck_results->assign(list);
383 }
384