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