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