| OLD | NEW |
| (Empty) |
| 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 <stddef.h> | |
| 8 #include <stdint.h> | |
| 9 #include <algorithm> | |
| 10 #include <utility> | |
| 11 | |
| 12 #include "base/bind.h" | |
| 13 #include "base/command_line.h" | |
| 14 #include "base/location.h" | |
| 15 #include "base/logging.h" | |
| 16 #include "base/macros.h" | |
| 17 #include "base/single_thread_task_runner.h" | |
| 18 #include "base/stl_util.h" | |
| 19 #include "base/threading/thread_task_runner_handle.h" | |
| 20 #include "build/build_config.h" | |
| 21 #include "chrome/common/channel_info.h" | |
| 22 #include "chrome/common/chrome_switches.h" | |
| 23 #include "chrome/common/spellcheck_common.h" | |
| 24 #include "chrome/common/spellcheck_messages.h" | |
| 25 #include "chrome/common/spellcheck_result.h" | |
| 26 #include "chrome/renderer/spellchecker/spellcheck_language.h" | |
| 27 #include "chrome/renderer/spellchecker/spellcheck_provider.h" | |
| 28 #include "content/public/renderer/render_thread.h" | |
| 29 #include "content/public/renderer/render_view.h" | |
| 30 #include "content/public/renderer/render_view_visitor.h" | |
| 31 #include "ipc/ipc_platform_file.h" | |
| 32 #include "third_party/WebKit/public/platform/WebString.h" | |
| 33 #include "third_party/WebKit/public/platform/WebVector.h" | |
| 34 #include "third_party/WebKit/public/web/WebTextCheckingCompletion.h" | |
| 35 #include "third_party/WebKit/public/web/WebTextCheckingResult.h" | |
| 36 #include "third_party/WebKit/public/web/WebTextDecorationType.h" | |
| 37 #include "third_party/WebKit/public/web/WebView.h" | |
| 38 | |
| 39 using blink::WebVector; | |
| 40 using blink::WebString; | |
| 41 using blink::WebTextCheckingResult; | |
| 42 using blink::WebTextDecorationType; | |
| 43 | |
| 44 namespace { | |
| 45 const int kNoOffset = 0; | |
| 46 const int kNoTag = 0; | |
| 47 | |
| 48 class UpdateSpellcheckEnabled : public content::RenderViewVisitor { | |
| 49 public: | |
| 50 explicit UpdateSpellcheckEnabled(bool enabled) : enabled_(enabled) {} | |
| 51 bool Visit(content::RenderView* render_view) override; | |
| 52 | |
| 53 private: | |
| 54 bool enabled_; // New spellcheck-enabled state. | |
| 55 DISALLOW_COPY_AND_ASSIGN(UpdateSpellcheckEnabled); | |
| 56 }; | |
| 57 | |
| 58 bool UpdateSpellcheckEnabled::Visit(content::RenderView* render_view) { | |
| 59 SpellCheckProvider* provider = SpellCheckProvider::Get(render_view); | |
| 60 DCHECK(provider); | |
| 61 provider->EnableSpellcheck(enabled_); | |
| 62 return true; | |
| 63 } | |
| 64 | |
| 65 class DocumentMarkersCollector : public content::RenderViewVisitor { | |
| 66 public: | |
| 67 DocumentMarkersCollector() {} | |
| 68 ~DocumentMarkersCollector() override {} | |
| 69 const std::vector<uint32_t>& markers() const { return markers_; } | |
| 70 bool Visit(content::RenderView* render_view) override; | |
| 71 | |
| 72 private: | |
| 73 std::vector<uint32_t> markers_; | |
| 74 DISALLOW_COPY_AND_ASSIGN(DocumentMarkersCollector); | |
| 75 }; | |
| 76 | |
| 77 bool DocumentMarkersCollector::Visit(content::RenderView* render_view) { | |
| 78 if (!render_view || !render_view->GetWebView()) | |
| 79 return true; | |
| 80 WebVector<uint32_t> markers; | |
| 81 render_view->GetWebView()->spellingMarkers(&markers); | |
| 82 for (size_t i = 0; i < markers.size(); ++i) | |
| 83 markers_.push_back(markers[i]); | |
| 84 // Visit all render views. | |
| 85 return true; | |
| 86 } | |
| 87 | |
| 88 class DocumentMarkersRemover : public content::RenderViewVisitor { | |
| 89 public: | |
| 90 explicit DocumentMarkersRemover(const std::set<std::string>& words); | |
| 91 ~DocumentMarkersRemover() override {} | |
| 92 bool Visit(content::RenderView* render_view) override; | |
| 93 | |
| 94 private: | |
| 95 WebVector<WebString> words_; | |
| 96 DISALLOW_COPY_AND_ASSIGN(DocumentMarkersRemover); | |
| 97 }; | |
| 98 | |
| 99 DocumentMarkersRemover::DocumentMarkersRemover( | |
| 100 const std::set<std::string>& words) | |
| 101 : words_(words.size()) { | |
| 102 std::transform(words.begin(), words.end(), words_.begin(), | |
| 103 [](const std::string& w) { return WebString::fromUTF8(w); }); | |
| 104 } | |
| 105 | |
| 106 bool DocumentMarkersRemover::Visit(content::RenderView* render_view) { | |
| 107 if (render_view && render_view->GetWebView()) | |
| 108 render_view->GetWebView()->removeSpellingMarkersUnderWords(words_); | |
| 109 return true; | |
| 110 } | |
| 111 | |
| 112 bool IsApostrophe(base::char16 c) { | |
| 113 const base::char16 kApostrophe = 0x27; | |
| 114 const base::char16 kRightSingleQuotationMark = 0x2019; | |
| 115 return c == kApostrophe || c == kRightSingleQuotationMark; | |
| 116 } | |
| 117 | |
| 118 // Makes sure that the apostrophes in the |spelling_suggestion| are the same | |
| 119 // type as in the |misspelled_word| and in the same order. Ignore differences in | |
| 120 // the number of apostrophes. | |
| 121 void PreserveOriginalApostropheTypes(const base::string16& misspelled_word, | |
| 122 base::string16* spelling_suggestion) { | |
| 123 auto it = spelling_suggestion->begin(); | |
| 124 for (const base::char16& c : misspelled_word) { | |
| 125 if (IsApostrophe(c)) { | |
| 126 it = std::find_if(it, spelling_suggestion->end(), IsApostrophe); | |
| 127 if (it == spelling_suggestion->end()) | |
| 128 return; | |
| 129 | |
| 130 *it++ = c; | |
| 131 } | |
| 132 } | |
| 133 } | |
| 134 | |
| 135 } // namespace | |
| 136 | |
| 137 class SpellCheck::SpellcheckRequest { | |
| 138 public: | |
| 139 SpellcheckRequest(const base::string16& text, | |
| 140 blink::WebTextCheckingCompletion* completion) | |
| 141 : text_(text), completion_(completion) { | |
| 142 DCHECK(completion); | |
| 143 } | |
| 144 ~SpellcheckRequest() {} | |
| 145 | |
| 146 base::string16 text() { return text_; } | |
| 147 blink::WebTextCheckingCompletion* completion() { return completion_; } | |
| 148 | |
| 149 private: | |
| 150 base::string16 text_; // Text to be checked in this task. | |
| 151 | |
| 152 // The interface to send the misspelled ranges to WebKit. | |
| 153 blink::WebTextCheckingCompletion* completion_; | |
| 154 | |
| 155 DISALLOW_COPY_AND_ASSIGN(SpellcheckRequest); | |
| 156 }; | |
| 157 | |
| 158 | |
| 159 // Initializes SpellCheck object. | |
| 160 // spellcheck_enabled_ currently MUST be set to true, due to peculiarities of | |
| 161 // the initialization sequence. | |
| 162 // Since it defaults to true, newly created SpellCheckProviders will enable | |
| 163 // spellchecking. After the first word is typed, the provider requests a check, | |
| 164 // which in turn triggers the delayed initialization sequence in SpellCheck. | |
| 165 // This does send a message to the browser side, which triggers the creation | |
| 166 // of the SpellcheckService. That does create the observer for the preference | |
| 167 // responsible for enabling/disabling checking, which allows subsequent changes | |
| 168 // to that preference to be sent to all SpellCheckProviders. | |
| 169 // Setting |spellcheck_enabled_| to false by default prevents that mechanism, | |
| 170 // and as such the SpellCheckProviders will never be notified of different | |
| 171 // values. | |
| 172 // TODO(groby): Simplify this. | |
| 173 SpellCheck::SpellCheck() : spellcheck_enabled_(true) {} | |
| 174 | |
| 175 SpellCheck::~SpellCheck() { | |
| 176 } | |
| 177 | |
| 178 void SpellCheck::FillSuggestions( | |
| 179 const std::vector<std::vector<base::string16>>& suggestions_list, | |
| 180 std::vector<base::string16>* optional_suggestions) { | |
| 181 DCHECK(optional_suggestions); | |
| 182 size_t num_languages = suggestions_list.size(); | |
| 183 | |
| 184 // Compute maximum number of suggestions in a single language. | |
| 185 size_t max_suggestions = 0; | |
| 186 for (const auto& suggestions : suggestions_list) | |
| 187 max_suggestions = std::max(max_suggestions, suggestions.size()); | |
| 188 | |
| 189 for (size_t count = 0; count < (max_suggestions * num_languages); ++count) { | |
| 190 size_t language = count % num_languages; | |
| 191 size_t index = count / num_languages; | |
| 192 | |
| 193 if (suggestions_list[language].size() <= index) | |
| 194 continue; | |
| 195 | |
| 196 const base::string16& suggestion = suggestions_list[language][index]; | |
| 197 // Only add the suggestion if it's unique. | |
| 198 if (!ContainsValue(*optional_suggestions, suggestion)) { | |
| 199 optional_suggestions->push_back(suggestion); | |
| 200 } | |
| 201 if (optional_suggestions->size() >= | |
| 202 chrome::spellcheck_common::kMaxSuggestions) { | |
| 203 break; | |
| 204 } | |
| 205 } | |
| 206 } | |
| 207 | |
| 208 bool SpellCheck::OnControlMessageReceived(const IPC::Message& message) { | |
| 209 bool handled = true; | |
| 210 IPC_BEGIN_MESSAGE_MAP(SpellCheck, message) | |
| 211 IPC_MESSAGE_HANDLER(SpellCheckMsg_Init, OnInit) | |
| 212 IPC_MESSAGE_HANDLER(SpellCheckMsg_CustomDictionaryChanged, | |
| 213 OnCustomDictionaryChanged) | |
| 214 IPC_MESSAGE_HANDLER(SpellCheckMsg_EnableSpellCheck, OnEnableSpellCheck) | |
| 215 IPC_MESSAGE_HANDLER(SpellCheckMsg_RequestDocumentMarkers, | |
| 216 OnRequestDocumentMarkers) | |
| 217 IPC_MESSAGE_UNHANDLED(handled = false) | |
| 218 IPC_END_MESSAGE_MAP() | |
| 219 | |
| 220 return handled; | |
| 221 } | |
| 222 | |
| 223 void SpellCheck::OnInit( | |
| 224 const std::vector<SpellCheckBDictLanguage>& bdict_languages, | |
| 225 const std::set<std::string>& custom_words) { | |
| 226 languages_.clear(); | |
| 227 for (const auto& bdict_language : bdict_languages) { | |
| 228 AddSpellcheckLanguage( | |
| 229 IPC::PlatformFileForTransitToFile(bdict_language.file), | |
| 230 bdict_language.language); | |
| 231 } | |
| 232 | |
| 233 custom_dictionary_.Init(custom_words); | |
| 234 #if !defined(USE_BROWSER_SPELLCHECKER) | |
| 235 PostDelayedSpellCheckTask(pending_request_param_.release()); | |
| 236 #endif | |
| 237 } | |
| 238 | |
| 239 void SpellCheck::OnCustomDictionaryChanged( | |
| 240 const std::set<std::string>& words_added, | |
| 241 const std::set<std::string>& words_removed) { | |
| 242 custom_dictionary_.OnCustomDictionaryChanged(words_added, words_removed); | |
| 243 if (words_added.empty()) | |
| 244 return; | |
| 245 DocumentMarkersRemover markersRemover(words_added); | |
| 246 content::RenderView::ForEach(&markersRemover); | |
| 247 } | |
| 248 | |
| 249 void SpellCheck::OnEnableSpellCheck(bool enable) { | |
| 250 spellcheck_enabled_ = enable; | |
| 251 UpdateSpellcheckEnabled updater(enable); | |
| 252 content::RenderView::ForEach(&updater); | |
| 253 } | |
| 254 | |
| 255 void SpellCheck::OnRequestDocumentMarkers() { | |
| 256 DocumentMarkersCollector collector; | |
| 257 content::RenderView::ForEach(&collector); | |
| 258 content::RenderThread::Get()->Send( | |
| 259 new SpellCheckHostMsg_RespondDocumentMarkers(collector.markers())); | |
| 260 } | |
| 261 | |
| 262 // TODO(groby): Make sure we always have a spelling engine, even before | |
| 263 // AddSpellcheckLanguage() is called. | |
| 264 void SpellCheck::AddSpellcheckLanguage(base::File file, | |
| 265 const std::string& language) { | |
| 266 languages_.push_back(new SpellcheckLanguage()); | |
| 267 languages_.back()->Init(std::move(file), language); | |
| 268 } | |
| 269 | |
| 270 bool SpellCheck::SpellCheckWord( | |
| 271 const base::char16* text_begin, | |
| 272 int position_in_text, | |
| 273 int text_length, | |
| 274 int tag, | |
| 275 int* misspelling_start, | |
| 276 int* misspelling_len, | |
| 277 std::vector<base::string16>* optional_suggestions) { | |
| 278 DCHECK(text_length >= position_in_text); | |
| 279 DCHECK(misspelling_start && misspelling_len) << "Out vars must be given."; | |
| 280 | |
| 281 // Do nothing if we need to delay initialization. (Rather than blocking, | |
| 282 // report the word as correctly spelled.) | |
| 283 if (InitializeIfNeeded()) | |
| 284 return true; | |
| 285 | |
| 286 // These are for holding misspelling or skippable word positions and lengths | |
| 287 // between calls to SpellcheckLanguage::SpellCheckWord. | |
| 288 int possible_misspelling_start; | |
| 289 int possible_misspelling_len; | |
| 290 // The longest sequence of text that all languages agree is skippable. | |
| 291 int agreed_skippable_len; | |
| 292 // A vector of vectors containing spelling suggestions from different | |
| 293 // languages. | |
| 294 std::vector<std::vector<base::string16>> suggestions_list; | |
| 295 // A vector to hold a language's misspelling suggestions between spellcheck | |
| 296 // calls. | |
| 297 std::vector<base::string16> language_suggestions; | |
| 298 | |
| 299 // This loop only advances if all languages agree that a sequence of text is | |
| 300 // skippable. | |
| 301 for (; position_in_text <= text_length; | |
| 302 position_in_text += agreed_skippable_len) { | |
| 303 // Reseting |agreed_skippable_len| to the worst-case length each time | |
| 304 // prevents some unnecessary iterations. | |
| 305 agreed_skippable_len = text_length; | |
| 306 *misspelling_start = 0; | |
| 307 *misspelling_len = 0; | |
| 308 suggestions_list.clear(); | |
| 309 | |
| 310 for (ScopedVector<SpellcheckLanguage>::iterator language = | |
| 311 languages_.begin(); | |
| 312 language != languages_.end();) { | |
| 313 language_suggestions.clear(); | |
| 314 SpellcheckLanguage::SpellcheckWordResult result = | |
| 315 (*language)->SpellCheckWord( | |
| 316 text_begin, position_in_text, text_length, tag, | |
| 317 &possible_misspelling_start, &possible_misspelling_len, | |
| 318 optional_suggestions ? &language_suggestions : nullptr); | |
| 319 | |
| 320 switch (result) { | |
| 321 case SpellcheckLanguage::SpellcheckWordResult::IS_CORRECT: | |
| 322 *misspelling_start = 0; | |
| 323 *misspelling_len = 0; | |
| 324 return true; | |
| 325 case SpellcheckLanguage::SpellcheckWordResult::IS_SKIPPABLE: | |
| 326 agreed_skippable_len = | |
| 327 std::min(agreed_skippable_len, possible_misspelling_len); | |
| 328 // If true, this means the spellchecker moved past a word that was | |
| 329 // previously determined to be misspelled or skippable, which means | |
| 330 // another spellcheck language marked it as correct. | |
| 331 if (position_in_text != possible_misspelling_start) { | |
| 332 *misspelling_len = 0; | |
| 333 position_in_text = possible_misspelling_start; | |
| 334 suggestions_list.clear(); | |
| 335 language = languages_.begin(); | |
| 336 } else { | |
| 337 language++; | |
| 338 } | |
| 339 break; | |
| 340 case SpellcheckLanguage::SpellcheckWordResult::IS_MISSPELLED: | |
| 341 *misspelling_start = possible_misspelling_start; | |
| 342 *misspelling_len = possible_misspelling_len; | |
| 343 // If true, this means the spellchecker moved past a word that was | |
| 344 // previously determined to be misspelled or skippable, which means | |
| 345 // another spellcheck language marked it as correct. | |
| 346 if (position_in_text != *misspelling_start) { | |
| 347 suggestions_list.clear(); | |
| 348 language = languages_.begin(); | |
| 349 position_in_text = *misspelling_start; | |
| 350 } else { | |
| 351 suggestions_list.push_back(language_suggestions); | |
| 352 language++; | |
| 353 } | |
| 354 break; | |
| 355 } | |
| 356 } | |
| 357 | |
| 358 // If |*misspelling_len| is non-zero, that means at least one language | |
| 359 // marked a word misspelled and no other language considered it correct. | |
| 360 if (*misspelling_len != 0) { | |
| 361 if (optional_suggestions) | |
| 362 FillSuggestions(suggestions_list, optional_suggestions); | |
| 363 return false; | |
| 364 } | |
| 365 } | |
| 366 | |
| 367 NOTREACHED(); | |
| 368 return true; | |
| 369 } | |
| 370 | |
| 371 bool SpellCheck::SpellCheckParagraph( | |
| 372 const base::string16& text, | |
| 373 WebVector<WebTextCheckingResult>* results) { | |
| 374 #if !defined(USE_BROWSER_SPELLCHECKER) | |
| 375 // Mac and Android have their own spell checkers,so this method won't be used | |
| 376 DCHECK(results); | |
| 377 std::vector<WebTextCheckingResult> textcheck_results; | |
| 378 size_t length = text.length(); | |
| 379 size_t position_in_text = 0; | |
| 380 | |
| 381 // Spellcheck::SpellCheckWord() automatically breaks text into words and | |
| 382 // checks the spellings of the extracted words. This function sets the | |
| 383 // position and length of the first misspelled word and returns false when | |
| 384 // the text includes misspelled words. Therefore, we just repeat calling the | |
| 385 // function until it returns true to check the whole text. | |
| 386 int misspelling_start = 0; | |
| 387 int misspelling_length = 0; | |
| 388 while (position_in_text <= length) { | |
| 389 if (SpellCheckWord(text.c_str(), | |
| 390 position_in_text, | |
| 391 length, | |
| 392 kNoTag, | |
| 393 &misspelling_start, | |
| 394 &misspelling_length, | |
| 395 NULL)) { | |
| 396 results->assign(textcheck_results); | |
| 397 return true; | |
| 398 } | |
| 399 | |
| 400 if (!custom_dictionary_.SpellCheckWord( | |
| 401 text, misspelling_start, misspelling_length)) { | |
| 402 base::string16 replacement; | |
| 403 textcheck_results.push_back(WebTextCheckingResult( | |
| 404 blink::WebTextDecorationTypeSpelling, | |
| 405 misspelling_start, | |
| 406 misspelling_length, | |
| 407 replacement)); | |
| 408 } | |
| 409 position_in_text = misspelling_start + misspelling_length; | |
| 410 } | |
| 411 results->assign(textcheck_results); | |
| 412 return false; | |
| 413 #else | |
| 414 // This function is only invoked for spell checker functionality that runs | |
| 415 // on the render thread. OSX and Android builds don't have that. | |
| 416 NOTREACHED(); | |
| 417 return true; | |
| 418 #endif | |
| 419 } | |
| 420 | |
| 421 // OSX and Android use their own spell checkers | |
| 422 #if !defined(USE_BROWSER_SPELLCHECKER) | |
| 423 void SpellCheck::RequestTextChecking( | |
| 424 const base::string16& text, | |
| 425 blink::WebTextCheckingCompletion* completion) { | |
| 426 // Clean up the previous request before starting a new request. | |
| 427 if (pending_request_param_.get()) | |
| 428 pending_request_param_->completion()->didCancelCheckingText(); | |
| 429 | |
| 430 pending_request_param_.reset(new SpellcheckRequest( | |
| 431 text, completion)); | |
| 432 // We will check this text after we finish loading the hunspell dictionary. | |
| 433 if (InitializeIfNeeded()) | |
| 434 return; | |
| 435 | |
| 436 PostDelayedSpellCheckTask(pending_request_param_.release()); | |
| 437 } | |
| 438 #endif | |
| 439 | |
| 440 bool SpellCheck::InitializeIfNeeded() { | |
| 441 if (languages_.empty()) | |
| 442 return true; | |
| 443 | |
| 444 bool initialize_if_needed = false; | |
| 445 for (SpellcheckLanguage* language : languages_) | |
| 446 initialize_if_needed |= language->InitializeIfNeeded(); | |
| 447 | |
| 448 return initialize_if_needed; | |
| 449 } | |
| 450 | |
| 451 // OSX and Android don't have |pending_request_param_| | |
| 452 #if !defined(USE_BROWSER_SPELLCHECKER) | |
| 453 void SpellCheck::PostDelayedSpellCheckTask(SpellcheckRequest* request) { | |
| 454 if (!request) | |
| 455 return; | |
| 456 | |
| 457 base::ThreadTaskRunnerHandle::Get()->PostTask( | |
| 458 FROM_HERE, base::Bind(&SpellCheck::PerformSpellCheck, AsWeakPtr(), | |
| 459 base::Owned(request))); | |
| 460 } | |
| 461 #endif | |
| 462 | |
| 463 // Mac and Android use their platform engines instead. | |
| 464 #if !defined(USE_BROWSER_SPELLCHECKER) | |
| 465 void SpellCheck::PerformSpellCheck(SpellcheckRequest* param) { | |
| 466 DCHECK(param); | |
| 467 | |
| 468 if (languages_.empty() || | |
| 469 std::find_if(languages_.begin(), languages_.end(), | |
| 470 [](SpellcheckLanguage* language) { | |
| 471 return !language->IsEnabled(); | |
| 472 }) != languages_.end()) { | |
| 473 param->completion()->didCancelCheckingText(); | |
| 474 } else { | |
| 475 WebVector<blink::WebTextCheckingResult> results; | |
| 476 SpellCheckParagraph(param->text(), &results); | |
| 477 param->completion()->didFinishCheckingText(results); | |
| 478 } | |
| 479 } | |
| 480 #endif | |
| 481 | |
| 482 void SpellCheck::CreateTextCheckingResults( | |
| 483 ResultFilter filter, | |
| 484 int line_offset, | |
| 485 const base::string16& line_text, | |
| 486 const std::vector<SpellCheckResult>& spellcheck_results, | |
| 487 WebVector<WebTextCheckingResult>* textcheck_results) { | |
| 488 DCHECK(!line_text.empty()); | |
| 489 | |
| 490 std::vector<WebTextCheckingResult> results; | |
| 491 for (const SpellCheckResult& spellcheck_result : spellcheck_results) { | |
| 492 DCHECK_LE(static_cast<size_t>(spellcheck_result.location), | |
| 493 line_text.length()); | |
| 494 DCHECK_LE(static_cast<size_t>(spellcheck_result.location + | |
| 495 spellcheck_result.length), | |
| 496 line_text.length()); | |
| 497 | |
| 498 const base::string16& misspelled_word = | |
| 499 line_text.substr(spellcheck_result.location, spellcheck_result.length); | |
| 500 base::string16 replacement = spellcheck_result.replacement; | |
| 501 SpellCheckResult::Decoration decoration = spellcheck_result.decoration; | |
| 502 | |
| 503 // Ignore words in custom dictionary. | |
| 504 if (custom_dictionary_.SpellCheckWord(misspelled_word, 0, | |
| 505 misspelled_word.length())) { | |
| 506 continue; | |
| 507 } | |
| 508 | |
| 509 // Use the same types of appostrophes as in the mispelled word. | |
| 510 PreserveOriginalApostropheTypes(misspelled_word, &replacement); | |
| 511 | |
| 512 // Ignore misspellings due the typographical apostrophe. | |
| 513 if (misspelled_word == replacement) | |
| 514 continue; | |
| 515 | |
| 516 if (filter == USE_NATIVE_CHECKER) { | |
| 517 // Double-check misspelled words with out spellchecker and attach grammar | |
| 518 // markers to them if our spellchecker tells us they are correct words, | |
| 519 // i.e. they are probably contextually-misspelled words. | |
| 520 int unused_misspelling_start = 0; | |
| 521 int unused_misspelling_length = 0; | |
| 522 if (decoration == SpellCheckResult::SPELLING && | |
| 523 SpellCheckWord(misspelled_word.c_str(), kNoOffset, | |
| 524 misspelled_word.length(), kNoTag, | |
| 525 &unused_misspelling_start, &unused_misspelling_length, | |
| 526 nullptr)) { | |
| 527 decoration = SpellCheckResult::GRAMMAR; | |
| 528 } | |
| 529 } | |
| 530 | |
| 531 results.push_back(WebTextCheckingResult( | |
| 532 static_cast<WebTextDecorationType>(decoration), | |
| 533 line_offset + spellcheck_result.location, spellcheck_result.length, | |
| 534 replacement, spellcheck_result.hash)); | |
| 535 } | |
| 536 | |
| 537 textcheck_results->assign(results); | |
| 538 } | |
| 539 | |
| 540 bool SpellCheck::IsSpellcheckEnabled() { | |
| 541 #if defined(OS_ANDROID) | |
| 542 if (!base::CommandLine::ForCurrentProcess()->HasSwitch( | |
| 543 switches::kEnableAndroidSpellChecker)) { | |
| 544 return false; | |
| 545 } | |
| 546 #endif | |
| 547 return spellcheck_enabled_; | |
| 548 } | |
| OLD | NEW |