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/hunspell_engine.h"
      6 
      7 #include <algorithm>
      8 #include <iterator>
      9 
     10 #include "base/files/memory_mapped_file.h"
     11 #include "base/metrics/histogram.h"
     12 #include "base/time/time.h"
     13 #include "chrome/common/spellcheck_common.h"
     14 #include "chrome/common/spellcheck_messages.h"
     15 #include "content/public/renderer/render_thread.h"
     16 #include "third_party/hunspell/src/hunspell/hunspell.hxx"
     17 
     18 using base::TimeTicks;
     19 using content::RenderThread;
     20 
     21 namespace {
     22   // Maximum length of words we actually check.
     23   // 64 is the observed limits for OSX system checker.
     24   const size_t kMaxCheckedLen = 64;
     25 
     26   // Maximum length of words we provide suggestions for.
     27   // 24 is the observed limits for OSX system checker.
     28   const size_t kMaxSuggestLen = 24;
     29 
     30   COMPILE_ASSERT(kMaxCheckedLen <= size_t(MAXWORDLEN), MaxCheckedLen_too_long);
     31   COMPILE_ASSERT(kMaxSuggestLen <= kMaxCheckedLen, MaxSuggestLen_too_long);
     32 }  // namespace
     33 
     34 #if !defined(OS_MACOSX)
     35 SpellingEngine* CreateNativeSpellingEngine() {
     36   return new HunspellEngine();
     37 }
     38 #endif
     39 
     40 HunspellEngine::HunspellEngine()
     41     : hunspell_enabled_(false),
     42       initialized_(false),
     43       dictionary_requested_(false) {
     44   // Wait till we check the first word before doing any initializing.
     45 }
     46 
     47 HunspellEngine::~HunspellEngine() {
     48 }
     49 
     50 void HunspellEngine::Init(base::File file) {
     51   initialized_ = true;
     52   hunspell_.reset();
     53   bdict_file_.reset();
     54   file_ = file.Pass();
     55   hunspell_enabled_ = file_.IsValid();
     56   // Delay the actual initialization of hunspell until it is needed.
     57 }
     58 
     59 void HunspellEngine::InitializeHunspell() {
     60   if (hunspell_.get())
     61     return;
     62 
     63   bdict_file_.reset(new base::MemoryMappedFile);
     64 
     65   if (bdict_file_->Initialize(file_.Pass())) {
     66     TimeTicks debug_start_time = base::Histogram::DebugNow();
     67 
     68     hunspell_.reset(new Hunspell(bdict_file_->data(), bdict_file_->length()));
     69 
     70     DHISTOGRAM_TIMES("Spellcheck.InitTime",
     71                      base::Histogram::DebugNow() - debug_start_time);
     72   } else {
     73     NOTREACHED() << "Could not mmap spellchecker dictionary.";
     74   }
     75 }
     76 
     77 bool HunspellEngine::CheckSpelling(const base::string16& word_to_check,
     78                                    int tag) {
     79   // Assume all words that cannot be checked are valid. Since Chrome can't
     80   // offer suggestions on them, either, there's no point in flagging them to
     81   // the user.
     82   bool word_correct = true;
     83   std::string word_to_check_utf8(base::UTF16ToUTF8(word_to_check));
     84 
     85   // Limit the size of checked words.
     86   if (word_to_check_utf8.length() <= kMaxCheckedLen) {
     87     // If |hunspell_| is NULL here, an error has occurred, but it's better
     88     // to check rather than crash.
     89     if (hunspell_.get()) {
     90       // |hunspell_->spell| returns 0 if the word is misspelled.
     91       word_correct = (hunspell_->spell(word_to_check_utf8.c_str()) != 0);
     92     }
     93   }
     94 
     95   return word_correct;
     96 }
     97 
     98 void HunspellEngine::FillSuggestionList(
     99     const base::string16& wrong_word,
    100     std::vector<base::string16>* optional_suggestions) {
    101   std::string wrong_word_utf8(base::UTF16ToUTF8(wrong_word));
    102   if (wrong_word_utf8.length() > kMaxSuggestLen)
    103     return;
    104 
    105   // If |hunspell_| is NULL here, an error has occurred, but it's better
    106   // to check rather than crash.
    107   // TODO(groby): Technically, it's not. We should track down the issue.
    108   if (!hunspell_.get())
    109     return;
    110 
    111   char** suggestions = NULL;
    112   int number_of_suggestions =
    113       hunspell_->suggest(&suggestions, wrong_word_utf8.c_str());
    114 
    115   // Populate the vector of WideStrings.
    116   for (int i = 0; i < number_of_suggestions; ++i) {
    117     if (i < chrome::spellcheck_common::kMaxSuggestions)
    118       optional_suggestions->push_back(base::UTF8ToUTF16(suggestions[i]));
    119     free(suggestions[i]);
    120   }
    121   if (suggestions != NULL)
    122     free(suggestions);
    123 }
    124 
    125 bool HunspellEngine::InitializeIfNeeded() {
    126   if (!initialized_ && !dictionary_requested_) {
    127     // RenderThread will not exist in test.
    128     if (RenderThread::Get())
    129       RenderThread::Get()->Send(new SpellCheckHostMsg_RequestDictionary);
    130     dictionary_requested_ = true;
    131     return true;
    132   }
    133 
    134   // Don't initialize if hunspell is disabled.
    135   if (file_.IsValid())
    136     InitializeHunspell();
    137 
    138   return !initialized_;
    139 }
    140 
    141 bool HunspellEngine::IsEnabled() {
    142   return hunspell_enabled_;
    143 }
    144