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 } 33 34 #if !defined(OS_MACOSX) 35 SpellingEngine* CreateNativeSpellingEngine() { 36 return new HunspellEngine(); 37 } 38 #endif 39 40 HunspellEngine::HunspellEngine() 41 : file_(base::kInvalidPlatformFileValue), 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::PlatformFile file) { 51 initialized_ = true; 52 hunspell_.reset(); 53 bdict_file_.reset(); 54 file_ = file; 55 // Delay the actual initialization of hunspell until it is needed. 56 } 57 58 void HunspellEngine::InitializeHunspell() { 59 if (hunspell_.get()) 60 return; 61 62 bdict_file_.reset(new base::MemoryMappedFile); 63 64 if (bdict_file_->Initialize(file_)) { 65 TimeTicks debug_start_time = base::Histogram::DebugNow(); 66 67 hunspell_.reset( 68 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 string16& word_to_check, int tag) { 78 // Assume all words that cannot be checked are valid. Since Chrome can't 79 // offer suggestions on them, either, there's no point in flagging them to 80 // the user. 81 bool word_correct = true; 82 std::string word_to_check_utf8(UTF16ToUTF8(word_to_check)); 83 84 // Limit the size of checked words. 85 if (word_to_check_utf8.length() <= kMaxCheckedLen) { 86 // If |hunspell_| is NULL here, an error has occurred, but it's better 87 // to check rather than crash. 88 if (hunspell_.get()) { 89 // |hunspell_->spell| returns 0 if the word is misspelled. 90 word_correct = (hunspell_->spell(word_to_check_utf8.c_str()) != 0); 91 } 92 } 93 94 return word_correct; 95 } 96 97 void HunspellEngine::FillSuggestionList( 98 const string16& wrong_word, 99 std::vector<string16>* optional_suggestions) { 100 std::string wrong_word_utf8(UTF16ToUTF8(wrong_word)); 101 if (wrong_word_utf8.length() > kMaxSuggestLen) 102 return; 103 104 // If |hunspell_| is NULL here, an error has occurred, but it's better 105 // to check rather than crash. 106 // TODO(groby): Technically, it's not. We should track down the issue. 107 if (!hunspell_.get()) 108 return; 109 110 char** suggestions = NULL; 111 int number_of_suggestions = 112 hunspell_->suggest(&suggestions, wrong_word_utf8.c_str()); 113 114 // Populate the vector of WideStrings. 115 for (int i = 0; i < number_of_suggestions; ++i) { 116 if (i < chrome::spellcheck_common::kMaxSuggestions) 117 optional_suggestions->push_back(UTF8ToUTF16(suggestions[i])); 118 free(suggestions[i]); 119 } 120 if (suggestions != NULL) 121 free(suggestions); 122 } 123 124 bool HunspellEngine::InitializeIfNeeded() { 125 if (!initialized_ && !dictionary_requested_) { 126 // RenderThread will not exist in test. 127 if (RenderThread::Get()) 128 RenderThread::Get()->Send(new SpellCheckHostMsg_RequestDictionary); 129 dictionary_requested_ = true; 130 return true; 131 } 132 133 // Don't initialize if hunspell is disabled. 134 if (file_ != base::kInvalidPlatformFileValue) 135 InitializeHunspell(); 136 137 return !initialized_; 138 } 139 140 bool HunspellEngine::IsEnabled() { 141 return file_ != base::kInvalidPlatformFileValue; 142 } 143