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)
CreateNativeSpellingEngine()35 SpellingEngine* CreateNativeSpellingEngine() {
36 return new HunspellEngine();
37 }
38 #endif
39
HunspellEngine()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
~HunspellEngine()47 HunspellEngine::~HunspellEngine() {
48 }
49
Init(base::PlatformFile file)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
InitializeHunspell()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
CheckSpelling(const base::string16 & word_to_check,int tag)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(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
FillSuggestionList(const base::string16 & wrong_word,std::vector<base::string16> * optional_suggestions)98 void 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
InitializeIfNeeded()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_ != base::kInvalidPlatformFileValue)
136 InitializeHunspell();
137
138 return !initialized_;
139 }
140
IsEnabled()141 bool HunspellEngine::IsEnabled() {
142 return file_ != base::kInvalidPlatformFileValue;
143 }
144