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