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::WebTextCheckingResult;
     26 using blink::WebTextDecorationType;
     27 
     28 namespace {
     29 
     30 class UpdateSpellcheckEnabled : public content::RenderViewVisitor {
     31  public:
     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 
     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:
     49   DocumentMarkersCollector() {}
     50   virtual ~DocumentMarkersCollector() {}
     51   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 
     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:
     74   SpellcheckRequest(const base::string16& text,
     75                     blink::WebTextCheckingCompletion* completion)
     76       : text_(text), completion_(completion) {
     77     DCHECK(completion);
     78   }
     79   ~SpellcheckRequest() {}
     80 
     81   base::string16 text() { return text_; }
     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.
    108 SpellCheck::SpellCheck()
    109     : auto_spell_correct_turned_on_(false),
    110       spellcheck_enabled_(true) {
    111 }
    112 
    113 SpellCheck::~SpellCheck() {
    114 }
    115 
    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 
    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 
    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 
    151 void SpellCheck::OnEnableAutoSpellCorrect(bool enable) {
    152   auto_spell_correct_turned_on_ = enable;
    153 }
    154 
    155 void SpellCheck::OnEnableSpellCheck(bool enable) {
    156   spellcheck_enabled_ = enable;
    157   UpdateSpellcheckEnabled updater(enable);
    158   content::RenderView::ForEach(&updater);
    159 }
    160 
    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.
    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 
    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 
    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 
    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
    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 
    318 bool SpellCheck::InitializeIfNeeded() {
    319   return spellcheck_.InitializeIfNeeded();
    320 }
    321 
    322 #if !defined(OS_MACOSX) // OSX doesn't have |pending_request_param_|
    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.
    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 
    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