Roll src/third_party/WebKit eac3800:0237a66 (svn 202606:202607)
[chromium-blink-merge.git] / chrome / renderer / spellchecker / spellcheck.cc
blobe162c5f1ddac2a2b00071a8a21193ce279bc9e02
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/sys_info.h"
16 #include "base/thread_task_runner_handle.h"
17 #include "chrome/common/channel_info.h"
18 #include "chrome/common/chrome_switches.h"
19 #include "chrome/common/spellcheck_common.h"
20 #include "chrome/common/spellcheck_messages.h"
21 #include "chrome/common/spellcheck_result.h"
22 #include "chrome/renderer/spellchecker/spellcheck_language.h"
23 #include "chrome/renderer/spellchecker/spellcheck_provider.h"
24 #include "components/version_info/version_info.h"
25 #include "content/public/renderer/render_thread.h"
26 #include "content/public/renderer/render_view.h"
27 #include "content/public/renderer/render_view_visitor.h"
28 #include "ipc/ipc_platform_file.h"
29 #include "third_party/WebKit/public/platform/WebString.h"
30 #include "third_party/WebKit/public/platform/WebVector.h"
31 #include "third_party/WebKit/public/web/WebTextCheckingCompletion.h"
32 #include "third_party/WebKit/public/web/WebTextCheckingResult.h"
33 #include "third_party/WebKit/public/web/WebTextDecorationType.h"
34 #include "third_party/WebKit/public/web/WebView.h"
36 using blink::WebVector;
37 using blink::WebString;
38 using blink::WebTextCheckingResult;
39 using blink::WebTextDecorationType;
41 namespace {
42 const int kNoOffset = 0;
43 const int kNoTag = 0;
45 class UpdateSpellcheckEnabled : public content::RenderViewVisitor {
46 public:
47 explicit UpdateSpellcheckEnabled(bool enabled) : enabled_(enabled) {}
48 bool Visit(content::RenderView* render_view) override;
50 private:
51 bool enabled_; // New spellcheck-enabled state.
52 DISALLOW_COPY_AND_ASSIGN(UpdateSpellcheckEnabled);
55 bool UpdateSpellcheckEnabled::Visit(content::RenderView* render_view) {
56 SpellCheckProvider* provider = SpellCheckProvider::Get(render_view);
57 DCHECK(provider);
58 provider->EnableSpellcheck(enabled_);
59 return true;
62 class DocumentMarkersCollector : public content::RenderViewVisitor {
63 public:
64 DocumentMarkersCollector() {}
65 ~DocumentMarkersCollector() override {}
66 const std::vector<uint32>& markers() const { return markers_; }
67 bool Visit(content::RenderView* render_view) override;
69 private:
70 std::vector<uint32> markers_;
71 DISALLOW_COPY_AND_ASSIGN(DocumentMarkersCollector);
74 bool DocumentMarkersCollector::Visit(content::RenderView* render_view) {
75 if (!render_view || !render_view->GetWebView())
76 return true;
77 WebVector<uint32> markers;
78 render_view->GetWebView()->spellingMarkers(&markers);
79 for (size_t i = 0; i < markers.size(); ++i)
80 markers_.push_back(markers[i]);
81 // Visit all render views.
82 return true;
85 class DocumentMarkersRemover : public content::RenderViewVisitor {
86 public:
87 explicit DocumentMarkersRemover(const std::set<std::string>& words);
88 ~DocumentMarkersRemover() override {}
89 bool Visit(content::RenderView* render_view) override;
91 private:
92 WebVector<WebString> words_;
93 DISALLOW_COPY_AND_ASSIGN(DocumentMarkersRemover);
96 DocumentMarkersRemover::DocumentMarkersRemover(
97 const std::set<std::string>& words)
98 : words_(words.size()) {
99 std::transform(words.begin(), words.end(), words_.begin(),
100 [](const std::string& w) { return WebString::fromUTF8(w); });
103 bool DocumentMarkersRemover::Visit(content::RenderView* render_view) {
104 if (render_view && render_view->GetWebView())
105 render_view->GetWebView()->removeSpellingMarkersUnderWords(words_);
106 return true;
109 bool IsApostrophe(base::char16 c) {
110 const base::char16 kApostrophe = 0x27;
111 const base::char16 kRightSingleQuotationMark = 0x2019;
112 return c == kApostrophe || c == kRightSingleQuotationMark;
115 // Makes sure that the apostrophes in the |spelling_suggestion| are the same
116 // type as in the |misspelled_word| and in the same order. Ignore differences in
117 // the number of apostrophes.
118 void PreserveOriginalApostropheTypes(const base::string16& misspelled_word,
119 base::string16* spelling_suggestion) {
120 auto it = spelling_suggestion->begin();
121 for (const base::char16& c : misspelled_word) {
122 if (IsApostrophe(c)) {
123 it = std::find_if(it, spelling_suggestion->end(), IsApostrophe);
124 if (it == spelling_suggestion->end())
125 return;
127 *it++ = c;
132 } // namespace
134 class SpellCheck::SpellcheckRequest {
135 public:
136 SpellcheckRequest(const base::string16& text,
137 blink::WebTextCheckingCompletion* completion)
138 : text_(text), completion_(completion) {
139 DCHECK(completion);
141 ~SpellcheckRequest() {}
143 base::string16 text() { return text_; }
144 blink::WebTextCheckingCompletion* completion() { return completion_; }
146 private:
147 base::string16 text_; // Text to be checked in this task.
149 // The interface to send the misspelled ranges to WebKit.
150 blink::WebTextCheckingCompletion* completion_;
152 DISALLOW_COPY_AND_ASSIGN(SpellcheckRequest);
156 // Initializes SpellCheck object.
157 // spellcheck_enabled_ currently MUST be set to true, due to peculiarities of
158 // the initialization sequence.
159 // Since it defaults to true, newly created SpellCheckProviders will enable
160 // spellchecking. After the first word is typed, the provider requests a check,
161 // which in turn triggers the delayed initialization sequence in SpellCheck.
162 // This does send a message to the browser side, which triggers the creation
163 // of the SpellcheckService. That does create the observer for the preference
164 // responsible for enabling/disabling checking, which allows subsequent changes
165 // to that preference to be sent to all SpellCheckProviders.
166 // Setting |spellcheck_enabled_| to false by default prevents that mechanism,
167 // and as such the SpellCheckProviders will never be notified of different
168 // values.
169 // TODO(groby): Simplify this.
170 SpellCheck::SpellCheck()
171 : auto_spell_correct_turned_on_(false),
172 spellcheck_enabled_(true) {
175 SpellCheck::~SpellCheck() {
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();
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());
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;
193 if (suggestions_list[language].size() <= index)
194 continue;
196 const base::string16& suggestion = suggestions_list[language][index];
197 // Only add the suggestion if it's unique.
198 if (std::find(optional_suggestions->begin(), optional_suggestions->end(),
199 suggestion) == optional_suggestions->end()) {
200 optional_suggestions->push_back(suggestion);
202 if (optional_suggestions->size() >=
203 chrome::spellcheck_common::kMaxSuggestions) {
204 break;
209 bool SpellCheck::OnControlMessageReceived(const IPC::Message& message) {
210 bool handled = true;
211 IPC_BEGIN_MESSAGE_MAP(SpellCheck, message)
212 IPC_MESSAGE_HANDLER(SpellCheckMsg_Init, OnInit)
213 IPC_MESSAGE_HANDLER(SpellCheckMsg_CustomDictionaryChanged,
214 OnCustomDictionaryChanged)
215 IPC_MESSAGE_HANDLER(SpellCheckMsg_EnableAutoSpellCorrect,
216 OnEnableAutoSpellCorrect)
217 IPC_MESSAGE_HANDLER(SpellCheckMsg_EnableSpellCheck, OnEnableSpellCheck)
218 IPC_MESSAGE_HANDLER(SpellCheckMsg_RequestDocumentMarkers,
219 OnRequestDocumentMarkers)
220 IPC_MESSAGE_UNHANDLED(handled = false)
221 IPC_END_MESSAGE_MAP()
223 return handled;
226 void SpellCheck::OnInit(
227 const std::vector<SpellCheckBDictLanguage>& bdict_languages,
228 const std::set<std::string>& custom_words,
229 bool auto_spell_correct) {
230 languages_.clear();
231 for (const auto& bdict_language : bdict_languages) {
232 AddSpellcheckLanguage(
233 IPC::PlatformFileForTransitToFile(bdict_language.file),
234 bdict_language.language);
237 custom_dictionary_.Init(custom_words);
238 auto_spell_correct_turned_on_ = auto_spell_correct;
239 #if !defined(USE_BROWSER_SPELLCHECKER)
240 PostDelayedSpellCheckTask(pending_request_param_.release());
241 #endif
244 void SpellCheck::OnCustomDictionaryChanged(
245 const std::set<std::string>& words_added,
246 const std::set<std::string>& words_removed) {
247 custom_dictionary_.OnCustomDictionaryChanged(words_added, words_removed);
248 if (words_added.empty())
249 return;
250 DocumentMarkersRemover markersRemover(words_added);
251 content::RenderView::ForEach(&markersRemover);
254 void SpellCheck::OnEnableAutoSpellCorrect(bool enable) {
255 auto_spell_correct_turned_on_ = enable;
258 void SpellCheck::OnEnableSpellCheck(bool enable) {
259 spellcheck_enabled_ = enable;
260 UpdateSpellcheckEnabled updater(enable);
261 content::RenderView::ForEach(&updater);
264 void SpellCheck::OnRequestDocumentMarkers() {
265 DocumentMarkersCollector collector;
266 content::RenderView::ForEach(&collector);
267 content::RenderThread::Get()->Send(
268 new SpellCheckHostMsg_RespondDocumentMarkers(collector.markers()));
271 // TODO(groby): Make sure we always have a spelling engine, even before
272 // AddSpellcheckLanguage() is called.
273 void SpellCheck::AddSpellcheckLanguage(base::File file,
274 const std::string& language) {
275 languages_.push_back(new SpellcheckLanguage());
276 languages_.back()->Init(file.Pass(), language);
279 bool SpellCheck::SpellCheckWord(
280 const base::char16* text_begin,
281 int position_in_text,
282 int text_length,
283 int tag,
284 int* misspelling_start,
285 int* misspelling_len,
286 std::vector<base::string16>* optional_suggestions) {
287 DCHECK(text_length >= position_in_text);
288 DCHECK(misspelling_start && misspelling_len) << "Out vars must be given.";
290 // Do nothing if we need to delay initialization. (Rather than blocking,
291 // report the word as correctly spelled.)
292 if (InitializeIfNeeded())
293 return true;
295 // These are for holding misspelling or skippable word positions and lengths
296 // between calls to SpellcheckLanguage::SpellCheckWord.
297 int possible_misspelling_start;
298 int possible_misspelling_len;
299 // The longest sequence of text that all languages agree is skippable.
300 int agreed_skippable_len;
301 // A vector of vectors containing spelling suggestions from different
302 // languages.
303 std::vector<std::vector<base::string16>> suggestions_list;
304 // A vector to hold a language's misspelling suggestions between spellcheck
305 // calls.
306 std::vector<base::string16> language_suggestions;
308 // This loop only advances if all languages agree that a sequence of text is
309 // skippable.
310 for (; position_in_text <= text_length;
311 position_in_text += agreed_skippable_len) {
312 // Reseting |agreed_skippable_len| to the worst-case length each time
313 // prevents some unnecessary iterations.
314 agreed_skippable_len = text_length;
315 *misspelling_start = 0;
316 *misspelling_len = 0;
317 suggestions_list.clear();
319 for (ScopedVector<SpellcheckLanguage>::iterator language =
320 languages_.begin();
321 language != languages_.end();) {
322 language_suggestions.clear();
323 SpellcheckLanguage::SpellcheckWordResult result =
324 (*language)->SpellCheckWord(
325 text_begin, position_in_text, text_length, tag,
326 &possible_misspelling_start, &possible_misspelling_len,
327 optional_suggestions ? &language_suggestions : nullptr);
329 switch (result) {
330 case SpellcheckLanguage::SpellcheckWordResult::IS_CORRECT:
331 *misspelling_start = 0;
332 *misspelling_len = 0;
333 return true;
334 case SpellcheckLanguage::SpellcheckWordResult::IS_SKIPPABLE:
335 agreed_skippable_len =
336 std::min(agreed_skippable_len, possible_misspelling_len);
337 // If true, this means the spellchecker moved past a word that was
338 // previously determined to be misspelled or skippable, which means
339 // another spellcheck language marked it as correct.
340 if (position_in_text != possible_misspelling_start) {
341 *misspelling_len = 0;
342 position_in_text = possible_misspelling_start;
343 suggestions_list.clear();
344 language = languages_.begin();
345 } else {
346 language++;
348 break;
349 case SpellcheckLanguage::SpellcheckWordResult::IS_MISSPELLED:
350 *misspelling_start = possible_misspelling_start;
351 *misspelling_len = possible_misspelling_len;
352 // If true, this means the spellchecker moved past a word that was
353 // previously determined to be misspelled or skippable, which means
354 // another spellcheck language marked it as correct.
355 if (position_in_text != *misspelling_start) {
356 suggestions_list.clear();
357 language = languages_.begin();
358 position_in_text = *misspelling_start;
359 } else {
360 suggestions_list.push_back(language_suggestions);
361 language++;
363 break;
367 // If |*misspelling_len| is non-zero, that means at least one language
368 // marked a word misspelled and no other language considered it correct.
369 if (*misspelling_len != 0) {
370 if (optional_suggestions)
371 FillSuggestions(suggestions_list, optional_suggestions);
372 return false;
376 NOTREACHED();
377 return true;
380 bool SpellCheck::SpellCheckParagraph(
381 const base::string16& text,
382 WebVector<WebTextCheckingResult>* results) {
383 #if !defined(USE_BROWSER_SPELLCHECKER)
384 // Mac and Android have their own spell checkers,so this method won't be used
385 DCHECK(results);
386 std::vector<WebTextCheckingResult> textcheck_results;
387 size_t length = text.length();
388 size_t position_in_text = 0;
390 // Spellcheck::SpellCheckWord() automatically breaks text into words and
391 // checks the spellings of the extracted words. This function sets the
392 // position and length of the first misspelled word and returns false when
393 // the text includes misspelled words. Therefore, we just repeat calling the
394 // function until it returns true to check the whole text.
395 int misspelling_start = 0;
396 int misspelling_length = 0;
397 while (position_in_text <= length) {
398 if (SpellCheckWord(text.c_str(),
399 position_in_text,
400 length,
401 kNoTag,
402 &misspelling_start,
403 &misspelling_length,
404 NULL)) {
405 results->assign(textcheck_results);
406 return true;
409 if (!custom_dictionary_.SpellCheckWord(
410 text, misspelling_start, misspelling_length)) {
411 base::string16 replacement;
412 textcheck_results.push_back(WebTextCheckingResult(
413 blink::WebTextDecorationTypeSpelling,
414 misspelling_start,
415 misspelling_length,
416 replacement));
418 position_in_text = misspelling_start + misspelling_length;
420 results->assign(textcheck_results);
421 return false;
422 #else
423 // This function is only invoked for spell checker functionality that runs
424 // on the render thread. OSX and Android builds don't have that.
425 NOTREACHED();
426 return true;
427 #endif
430 base::string16 SpellCheck::GetAutoCorrectionWord(const base::string16& word,
431 int tag) {
432 base::string16 autocorrect_word;
433 if (!auto_spell_correct_turned_on_)
434 return autocorrect_word; // Return the empty string.
436 int word_length = static_cast<int>(word.size());
437 if (word_length < 2 ||
438 word_length > chrome::spellcheck_common::kMaxAutoCorrectWordSize)
439 return autocorrect_word;
441 if (InitializeIfNeeded())
442 return autocorrect_word;
444 base::char16 misspelled_word[
445 chrome::spellcheck_common::kMaxAutoCorrectWordSize + 1];
446 const base::char16* word_char = word.c_str();
447 for (int i = 0; i <= chrome::spellcheck_common::kMaxAutoCorrectWordSize;
448 ++i) {
449 if (i >= word_length)
450 misspelled_word[i] = 0;
451 else
452 misspelled_word[i] = word_char[i];
455 // Swap adjacent characters and spellcheck.
456 int misspelling_start, misspelling_len;
457 for (int i = 0; i < word_length - 1; i++) {
458 // Swap.
459 std::swap(misspelled_word[i], misspelled_word[i + 1]);
461 // Check spelling.
462 misspelling_start = misspelling_len = 0;
463 SpellCheckWord(misspelled_word, kNoOffset, word_length, tag,
464 &misspelling_start, &misspelling_len, NULL);
466 // Make decision: if only one swap produced a valid word, then we want to
467 // return it. If we found two or more, we don't do autocorrection.
468 if (misspelling_len == 0) {
469 if (autocorrect_word.empty()) {
470 autocorrect_word.assign(misspelled_word);
471 } else {
472 autocorrect_word.clear();
473 break;
477 // Restore the swapped characters.
478 std::swap(misspelled_word[i], misspelled_word[i + 1]);
480 return autocorrect_word;
483 // OSX and Android use their own spell checkers
484 #if !defined(USE_BROWSER_SPELLCHECKER)
485 void SpellCheck::RequestTextChecking(
486 const base::string16& text,
487 blink::WebTextCheckingCompletion* completion) {
488 // Clean up the previous request before starting a new request.
489 if (pending_request_param_.get())
490 pending_request_param_->completion()->didCancelCheckingText();
492 pending_request_param_.reset(new SpellcheckRequest(
493 text, completion));
494 // We will check this text after we finish loading the hunspell dictionary.
495 if (InitializeIfNeeded())
496 return;
498 PostDelayedSpellCheckTask(pending_request_param_.release());
500 #endif
502 bool SpellCheck::InitializeIfNeeded() {
503 if (languages_.empty())
504 return true;
506 bool initialize_if_needed = false;
507 for (SpellcheckLanguage* language : languages_)
508 initialize_if_needed |= language->InitializeIfNeeded();
510 return initialize_if_needed;
513 // OSX and Android don't have |pending_request_param_|
514 #if !defined(USE_BROWSER_SPELLCHECKER)
515 void SpellCheck::PostDelayedSpellCheckTask(SpellcheckRequest* request) {
516 if (!request)
517 return;
519 base::ThreadTaskRunnerHandle::Get()->PostTask(
520 FROM_HERE, base::Bind(&SpellCheck::PerformSpellCheck, AsWeakPtr(),
521 base::Owned(request)));
523 #endif
525 // Mac and Android use their platform engines instead.
526 #if !defined(USE_BROWSER_SPELLCHECKER)
527 void SpellCheck::PerformSpellCheck(SpellcheckRequest* param) {
528 DCHECK(param);
530 if (languages_.empty() ||
531 std::find_if(languages_.begin(), languages_.end(),
532 [](SpellcheckLanguage* language) {
533 return !language->IsEnabled();
534 }) != languages_.end()) {
535 param->completion()->didCancelCheckingText();
536 } else {
537 WebVector<blink::WebTextCheckingResult> results;
538 SpellCheckParagraph(param->text(), &results);
539 param->completion()->didFinishCheckingText(results);
542 #endif
544 void SpellCheck::CreateTextCheckingResults(
545 ResultFilter filter,
546 int line_offset,
547 const base::string16& line_text,
548 const std::vector<SpellCheckResult>& spellcheck_results,
549 WebVector<WebTextCheckingResult>* textcheck_results) {
550 DCHECK(!line_text.empty());
552 std::vector<WebTextCheckingResult> results;
553 for (const SpellCheckResult& spellcheck_result : spellcheck_results) {
554 DCHECK_LE(static_cast<size_t>(spellcheck_result.location),
555 line_text.length());
556 DCHECK_LE(static_cast<size_t>(spellcheck_result.location +
557 spellcheck_result.length),
558 line_text.length());
560 const base::string16& misspelled_word =
561 line_text.substr(spellcheck_result.location, spellcheck_result.length);
562 base::string16 replacement = spellcheck_result.replacement;
563 SpellCheckResult::Decoration decoration = spellcheck_result.decoration;
565 // Ignore words in custom dictionary.
566 if (custom_dictionary_.SpellCheckWord(misspelled_word, 0,
567 misspelled_word.length())) {
568 continue;
571 // Use the same types of appostrophes as in the mispelled word.
572 PreserveOriginalApostropheTypes(misspelled_word, &replacement);
574 // Ignore misspellings due the typographical apostrophe.
575 if (misspelled_word == replacement)
576 continue;
578 if (filter == USE_NATIVE_CHECKER) {
579 // Double-check misspelled words with out spellchecker and attach grammar
580 // markers to them if our spellchecker tells us they are correct words,
581 // i.e. they are probably contextually-misspelled words.
582 int unused_misspelling_start = 0;
583 int unused_misspelling_length = 0;
584 if (decoration == SpellCheckResult::SPELLING &&
585 SpellCheckWord(misspelled_word.c_str(), kNoOffset,
586 misspelled_word.length(), kNoTag,
587 &unused_misspelling_start, &unused_misspelling_length,
588 nullptr)) {
589 decoration = SpellCheckResult::GRAMMAR;
593 results.push_back(WebTextCheckingResult(
594 static_cast<WebTextDecorationType>(decoration),
595 line_offset + spellcheck_result.location, spellcheck_result.length,
596 replacement, spellcheck_result.hash));
599 textcheck_results->assign(results);
602 bool SpellCheck::IsSpellcheckEnabled() {
603 #if defined(OS_ANDROID)
604 if (base::SysInfo::IsLowEndDevice())
605 return false;
607 version_info::Channel channel = chrome::GetChannel();
608 if (channel == version_info::Channel::DEV ||
609 channel == version_info::Channel::CANARY) {
610 return true;
611 } else if (!base::CommandLine::ForCurrentProcess()->HasSwitch(
612 switches::kEnableAndroidSpellChecker)) {
613 return false;
615 #endif
616 return spellcheck_enabled_;