Roll src/third_party/WebKit d9c6159:8139f33 (svn 201974:201975)
[chromium-blink-merge.git] / chrome / renderer / spellchecker / spellcheck.cc
bloba836b2d5b8c395f510c4b0fbeb01c21117abeb86
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 // A vector containing the indices of the current suggestion for each
182 // language's suggestion list.
183 std::vector<size_t> indices(suggestions_list.size(), 0);
184 // Take one suggestion at a time from each language's suggestions and add it
185 // to |optional_suggestions|.
186 for (size_t i = 0, num_empty = 0;
187 num_empty < suggestions_list.size() &&
188 optional_suggestions->size() <
189 chrome::spellcheck_common::kMaxSuggestions;
190 i = (i + 1) % suggestions_list.size()) {
191 if (indices[i] < suggestions_list[i].size()) {
192 const base::string16& suggestion = suggestions_list[i][indices[i]];
193 // Only add the suggestion if it's unique.
194 if (std::find(optional_suggestions->begin(), optional_suggestions->end(),
195 suggestion) == optional_suggestions->end()) {
196 optional_suggestions->push_back(suggestion);
198 if (++indices[i] == suggestions_list[i].size())
199 num_empty++;
204 bool SpellCheck::OnControlMessageReceived(const IPC::Message& message) {
205 bool handled = true;
206 IPC_BEGIN_MESSAGE_MAP(SpellCheck, message)
207 IPC_MESSAGE_HANDLER(SpellCheckMsg_Init, OnInit)
208 IPC_MESSAGE_HANDLER(SpellCheckMsg_CustomDictionaryChanged,
209 OnCustomDictionaryChanged)
210 IPC_MESSAGE_HANDLER(SpellCheckMsg_EnableAutoSpellCorrect,
211 OnEnableAutoSpellCorrect)
212 IPC_MESSAGE_HANDLER(SpellCheckMsg_EnableSpellCheck, OnEnableSpellCheck)
213 IPC_MESSAGE_HANDLER(SpellCheckMsg_RequestDocumentMarkers,
214 OnRequestDocumentMarkers)
215 IPC_MESSAGE_UNHANDLED(handled = false)
216 IPC_END_MESSAGE_MAP()
218 return handled;
221 void SpellCheck::OnInit(
222 const std::vector<SpellCheckBDictLanguage>& bdict_languages,
223 const std::set<std::string>& custom_words,
224 bool auto_spell_correct) {
225 languages_.clear();
226 for (const auto& bdict_language : bdict_languages) {
227 AddSpellcheckLanguage(
228 IPC::PlatformFileForTransitToFile(bdict_language.file),
229 bdict_language.language);
232 custom_dictionary_.Init(custom_words);
233 auto_spell_correct_turned_on_ = auto_spell_correct;
234 #if !defined(USE_BROWSER_SPELLCHECKER)
235 PostDelayedSpellCheckTask(pending_request_param_.release());
236 #endif
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);
249 void SpellCheck::OnEnableAutoSpellCorrect(bool enable) {
250 auto_spell_correct_turned_on_ = enable;
253 void SpellCheck::OnEnableSpellCheck(bool enable) {
254 spellcheck_enabled_ = enable;
255 UpdateSpellcheckEnabled updater(enable);
256 content::RenderView::ForEach(&updater);
259 void SpellCheck::OnRequestDocumentMarkers() {
260 DocumentMarkersCollector collector;
261 content::RenderView::ForEach(&collector);
262 content::RenderThread::Get()->Send(
263 new SpellCheckHostMsg_RespondDocumentMarkers(collector.markers()));
266 // TODO(groby): Make sure we always have a spelling engine, even before
267 // AddSpellcheckLanguage() is called.
268 void SpellCheck::AddSpellcheckLanguage(base::File file,
269 const std::string& language) {
270 languages_.push_back(new SpellcheckLanguage());
271 languages_.back()->Init(file.Pass(), language);
274 bool SpellCheck::SpellCheckWord(
275 const base::char16* text_begin,
276 int position_in_text,
277 int text_length,
278 int tag,
279 int* misspelling_start,
280 int* misspelling_len,
281 std::vector<base::string16>* optional_suggestions) {
282 DCHECK(text_length >= position_in_text);
283 DCHECK(misspelling_start && misspelling_len) << "Out vars must be given.";
285 // Do nothing if we need to delay initialization. (Rather than blocking,
286 // report the word as correctly spelled.)
287 if (InitializeIfNeeded())
288 return true;
290 // These are for holding misspelling or skippable word positions and lengths
291 // between calls to SpellcheckLanguage::SpellCheckWord.
292 int possible_misspelling_start;
293 int possible_misspelling_len;
294 // The longest sequence of text that all languages agree is skippable.
295 int agreed_skippable_len;
296 // A vector of vectors containing spelling suggestions from different
297 // languages.
298 std::vector<std::vector<base::string16>> suggestions_list;
299 // A vector to hold a language's misspelling suggestions between spellcheck
300 // calls.
301 std::vector<base::string16> language_suggestions;
303 // This loop only advances if all languages agree that a sequence of text is
304 // skippable.
305 for (; position_in_text <= text_length;
306 position_in_text += agreed_skippable_len) {
307 // Reseting |agreed_skippable_len| to the worst-case length each time
308 // prevents some unnecessary iterations.
309 agreed_skippable_len = text_length;
310 *misspelling_start = 0;
311 *misspelling_len = 0;
312 suggestions_list.clear();
314 for (ScopedVector<SpellcheckLanguage>::iterator language =
315 languages_.begin();
316 language != languages_.end();) {
317 language_suggestions.clear();
318 SpellcheckLanguage::SpellcheckWordResult result =
319 (*language)->SpellCheckWord(
320 text_begin, position_in_text, text_length, tag,
321 &possible_misspelling_start, &possible_misspelling_len,
322 optional_suggestions ? &language_suggestions : nullptr);
324 switch (result) {
325 case SpellcheckLanguage::SpellcheckWordResult::IS_CORRECT:
326 *misspelling_start = 0;
327 *misspelling_len = 0;
328 return true;
329 case SpellcheckLanguage::SpellcheckWordResult::IS_SKIPPABLE:
330 agreed_skippable_len =
331 std::min(agreed_skippable_len, possible_misspelling_len);
332 // If true, this means the spellchecker moved past a word that was
333 // previously determined to be misspelled or skippable, which means
334 // another spellcheck language marked it as correct.
335 if (position_in_text != possible_misspelling_start) {
336 *misspelling_len = 0;
337 position_in_text = possible_misspelling_start;
338 suggestions_list.clear();
339 language = languages_.begin();
340 } else {
341 language++;
343 break;
344 case SpellcheckLanguage::SpellcheckWordResult::IS_MISSPELLED:
345 *misspelling_start = possible_misspelling_start;
346 *misspelling_len = possible_misspelling_len;
347 // If true, this means the spellchecker moved past a word that was
348 // previously determined to be misspelled or skippable, which means
349 // another spellcheck language marked it as correct.
350 if (position_in_text != *misspelling_start) {
351 suggestions_list.clear();
352 language = languages_.begin();
353 position_in_text = *misspelling_start;
354 } else {
355 suggestions_list.push_back(language_suggestions);
356 language++;
358 break;
362 // If |*misspelling_len| is non-zero, that means at least one language
363 // marked a word misspelled and no other language considered it correct.
364 if (*misspelling_len != 0) {
365 if (optional_suggestions)
366 FillSuggestions(suggestions_list, optional_suggestions);
367 return false;
371 NOTREACHED();
372 return true;
375 bool SpellCheck::SpellCheckParagraph(
376 const base::string16& text,
377 WebVector<WebTextCheckingResult>* results) {
378 #if !defined(USE_BROWSER_SPELLCHECKER)
379 // Mac and Android have their own spell checkers,so this method won't be used
380 DCHECK(results);
381 std::vector<WebTextCheckingResult> textcheck_results;
382 size_t length = text.length();
383 size_t position_in_text = 0;
385 // Spellcheck::SpellCheckWord() automatically breaks text into words and
386 // checks the spellings of the extracted words. This function sets the
387 // position and length of the first misspelled word and returns false when
388 // the text includes misspelled words. Therefore, we just repeat calling the
389 // function until it returns true to check the whole text.
390 int misspelling_start = 0;
391 int misspelling_length = 0;
392 while (position_in_text <= length) {
393 if (SpellCheckWord(text.c_str(),
394 position_in_text,
395 length,
396 kNoTag,
397 &misspelling_start,
398 &misspelling_length,
399 NULL)) {
400 results->assign(textcheck_results);
401 return true;
404 if (!custom_dictionary_.SpellCheckWord(
405 text, misspelling_start, misspelling_length)) {
406 base::string16 replacement;
407 textcheck_results.push_back(WebTextCheckingResult(
408 blink::WebTextDecorationTypeSpelling,
409 misspelling_start,
410 misspelling_length,
411 replacement));
413 position_in_text = misspelling_start + misspelling_length;
415 results->assign(textcheck_results);
416 return false;
417 #else
418 // This function is only invoked for spell checker functionality that runs
419 // on the render thread. OSX and Android builds don't have that.
420 NOTREACHED();
421 return true;
422 #endif
425 base::string16 SpellCheck::GetAutoCorrectionWord(const base::string16& word,
426 int tag) {
427 base::string16 autocorrect_word;
428 if (!auto_spell_correct_turned_on_)
429 return autocorrect_word; // Return the empty string.
431 int word_length = static_cast<int>(word.size());
432 if (word_length < 2 ||
433 word_length > chrome::spellcheck_common::kMaxAutoCorrectWordSize)
434 return autocorrect_word;
436 if (InitializeIfNeeded())
437 return autocorrect_word;
439 base::char16 misspelled_word[
440 chrome::spellcheck_common::kMaxAutoCorrectWordSize + 1];
441 const base::char16* word_char = word.c_str();
442 for (int i = 0; i <= chrome::spellcheck_common::kMaxAutoCorrectWordSize;
443 ++i) {
444 if (i >= word_length)
445 misspelled_word[i] = 0;
446 else
447 misspelled_word[i] = word_char[i];
450 // Swap adjacent characters and spellcheck.
451 int misspelling_start, misspelling_len;
452 for (int i = 0; i < word_length - 1; i++) {
453 // Swap.
454 std::swap(misspelled_word[i], misspelled_word[i + 1]);
456 // Check spelling.
457 misspelling_start = misspelling_len = 0;
458 SpellCheckWord(misspelled_word, kNoOffset, word_length, tag,
459 &misspelling_start, &misspelling_len, NULL);
461 // Make decision: if only one swap produced a valid word, then we want to
462 // return it. If we found two or more, we don't do autocorrection.
463 if (misspelling_len == 0) {
464 if (autocorrect_word.empty()) {
465 autocorrect_word.assign(misspelled_word);
466 } else {
467 autocorrect_word.clear();
468 break;
472 // Restore the swapped characters.
473 std::swap(misspelled_word[i], misspelled_word[i + 1]);
475 return autocorrect_word;
478 // OSX and Android use their own spell checkers
479 #if !defined(USE_BROWSER_SPELLCHECKER)
480 void SpellCheck::RequestTextChecking(
481 const base::string16& text,
482 blink::WebTextCheckingCompletion* completion) {
483 // Clean up the previous request before starting a new request.
484 if (pending_request_param_.get())
485 pending_request_param_->completion()->didCancelCheckingText();
487 pending_request_param_.reset(new SpellcheckRequest(
488 text, completion));
489 // We will check this text after we finish loading the hunspell dictionary.
490 if (InitializeIfNeeded())
491 return;
493 PostDelayedSpellCheckTask(pending_request_param_.release());
495 #endif
497 bool SpellCheck::InitializeIfNeeded() {
498 if (languages_.empty())
499 return true;
501 bool initialize_if_needed = false;
502 for (SpellcheckLanguage* language : languages_)
503 initialize_if_needed |= language->InitializeIfNeeded();
505 return initialize_if_needed;
508 // OSX and Android don't have |pending_request_param_|
509 #if !defined(USE_BROWSER_SPELLCHECKER)
510 void SpellCheck::PostDelayedSpellCheckTask(SpellcheckRequest* request) {
511 if (!request)
512 return;
514 base::ThreadTaskRunnerHandle::Get()->PostTask(
515 FROM_HERE, base::Bind(&SpellCheck::PerformSpellCheck, AsWeakPtr(),
516 base::Owned(request)));
518 #endif
520 // Mac and Android use their platform engines instead.
521 #if !defined(USE_BROWSER_SPELLCHECKER)
522 void SpellCheck::PerformSpellCheck(SpellcheckRequest* param) {
523 DCHECK(param);
525 if (languages_.empty() ||
526 std::find_if(languages_.begin(), languages_.end(),
527 [](SpellcheckLanguage* language) {
528 return !language->IsEnabled();
529 }) != languages_.end()) {
530 param->completion()->didCancelCheckingText();
531 } else {
532 WebVector<blink::WebTextCheckingResult> results;
533 SpellCheckParagraph(param->text(), &results);
534 param->completion()->didFinishCheckingText(results);
537 #endif
539 void SpellCheck::CreateTextCheckingResults(
540 ResultFilter filter,
541 int line_offset,
542 const base::string16& line_text,
543 const std::vector<SpellCheckResult>& spellcheck_results,
544 WebVector<WebTextCheckingResult>* textcheck_results) {
545 DCHECK(!line_text.empty());
547 std::vector<WebTextCheckingResult> results;
548 for (const SpellCheckResult& spellcheck_result : spellcheck_results) {
549 DCHECK_LE(static_cast<size_t>(spellcheck_result.location),
550 line_text.length());
551 DCHECK_LE(static_cast<size_t>(spellcheck_result.location +
552 spellcheck_result.length),
553 line_text.length());
555 const base::string16& misspelled_word =
556 line_text.substr(spellcheck_result.location, spellcheck_result.length);
557 base::string16 replacement = spellcheck_result.replacement;
558 SpellCheckResult::Decoration decoration = spellcheck_result.decoration;
560 // Ignore words in custom dictionary.
561 if (custom_dictionary_.SpellCheckWord(misspelled_word, 0,
562 misspelled_word.length())) {
563 continue;
566 // Use the same types of appostrophes as in the mispelled word.
567 PreserveOriginalApostropheTypes(misspelled_word, &replacement);
569 // Ignore misspellings due the typographical apostrophe.
570 if (misspelled_word == replacement)
571 continue;
573 if (filter == USE_NATIVE_CHECKER) {
574 // Double-check misspelled words with out spellchecker and attach grammar
575 // markers to them if our spellchecker tells us they are correct words,
576 // i.e. they are probably contextually-misspelled words.
577 int unused_misspelling_start = 0;
578 int unused_misspelling_length = 0;
579 if (decoration == SpellCheckResult::SPELLING &&
580 SpellCheckWord(misspelled_word.c_str(), kNoOffset,
581 misspelled_word.length(), kNoTag,
582 &unused_misspelling_start, &unused_misspelling_length,
583 nullptr)) {
584 decoration = SpellCheckResult::GRAMMAR;
588 results.push_back(WebTextCheckingResult(
589 static_cast<WebTextDecorationType>(decoration),
590 line_offset + spellcheck_result.location, spellcheck_result.length,
591 replacement, spellcheck_result.hash));
594 textcheck_results->assign(results);
597 bool SpellCheck::IsSpellcheckEnabled() {
598 #if defined(OS_ANDROID)
599 if (base::SysInfo::IsLowEndDevice())
600 return false;
602 version_info::Channel channel = chrome::GetChannel();
603 if (channel == version_info::Channel::DEV ||
604 channel == version_info::Channel::CANARY) {
605 return true;
606 } else if (!base::CommandLine::ForCurrentProcess()->HasSwitch(
607 switches::kEnableAndroidSpellChecker)) {
608 return false;
610 #endif
611 return spellcheck_enabled_;