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 18using base::TimeTicks; 19using content::RenderThread; 20 21namespace { 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) 35SpellingEngine* CreateNativeSpellingEngine() { 36 return new HunspellEngine(); 37} 38#endif 39 40HunspellEngine::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 47HunspellEngine::~HunspellEngine() { 48} 49 50void 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 58void 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 77bool 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(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 98void HunspellEngine::FillSuggestionList( 99 const base::string16& wrong_word, 100 std::vector<base::string16>* optional_suggestions) { 101 std::string wrong_word_utf8(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(UTF8ToUTF16(suggestions[i])); 119 free(suggestions[i]); 120 } 121 if (suggestions != NULL) 122 free(suggestions); 123} 124 125bool 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_ != base::kInvalidPlatformFileValue) 136 InitializeHunspell(); 137 138 return !initialized_; 139} 140 141bool HunspellEngine::IsEnabled() { 142 return file_ != base::kInvalidPlatformFileValue; 143} 144