• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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