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