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"
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
;
42 const int kNoOffset
= 0;
45 class UpdateSpellcheckEnabled
: public content::RenderViewVisitor
{
47 explicit UpdateSpellcheckEnabled(bool enabled
) : enabled_(enabled
) {}
48 bool Visit(content::RenderView
* render_view
) override
;
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
);
58 provider
->EnableSpellcheck(enabled_
);
62 class DocumentMarkersCollector
: public content::RenderViewVisitor
{
64 DocumentMarkersCollector() {}
65 ~DocumentMarkersCollector() override
{}
66 const std::vector
<uint32
>& markers() const { return markers_
; }
67 bool Visit(content::RenderView
* render_view
) override
;
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())
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.
85 class DocumentMarkersRemover
: public content::RenderViewVisitor
{
87 explicit DocumentMarkersRemover(const std::set
<std::string
>& words
);
88 ~DocumentMarkersRemover() override
{}
89 bool Visit(content::RenderView
* render_view
) override
;
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_
);
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())
134 class SpellCheck::SpellcheckRequest
{
136 SpellcheckRequest(const base::string16
& text
,
137 blink::WebTextCheckingCompletion
* completion
)
138 : text_(text
), completion_(completion
) {
141 ~SpellcheckRequest() {}
143 base::string16
text() { return text_
; }
144 blink::WebTextCheckingCompletion
* completion() { return completion_
; }
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
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
)
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
) {
209 bool SpellCheck::OnControlMessageReceived(const IPC::Message
& message
) {
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()
226 void SpellCheck::OnInit(
227 const std::vector
<SpellCheckBDictLanguage
>& bdict_languages
,
228 const std::set
<std::string
>& custom_words
,
229 bool auto_spell_correct
) {
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());
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())
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
,
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())
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
303 std::vector
<std::vector
<base::string16
>> suggestions_list
;
304 // A vector to hold a language's misspelling suggestions between spellcheck
306 std::vector
<base::string16
> language_suggestions
;
308 // This loop only advances if all languages agree that a sequence of text is
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
=
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);
330 case SpellcheckLanguage::SpellcheckWordResult::IS_CORRECT
:
331 *misspelling_start
= 0;
332 *misspelling_len
= 0;
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();
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
;
360 suggestions_list
.push_back(language_suggestions
);
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
);
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
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(),
405 results
->assign(textcheck_results
);
409 if (!custom_dictionary_
.SpellCheckWord(
410 text
, misspelling_start
, misspelling_length
)) {
411 base::string16 replacement
;
412 textcheck_results
.push_back(WebTextCheckingResult(
413 blink::WebTextDecorationTypeSpelling
,
418 position_in_text
= misspelling_start
+ misspelling_length
;
420 results
->assign(textcheck_results
);
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.
430 base::string16
SpellCheck::GetAutoCorrectionWord(const base::string16
& word
,
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
;
449 if (i
>= word_length
)
450 misspelled_word
[i
] = 0;
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
++) {
459 std::swap(misspelled_word
[i
], misspelled_word
[i
+ 1]);
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
);
472 autocorrect_word
.clear();
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(
494 // We will check this text after we finish loading the hunspell dictionary.
495 if (InitializeIfNeeded())
498 PostDelayedSpellCheckTask(pending_request_param_
.release());
502 bool SpellCheck::InitializeIfNeeded() {
503 if (languages_
.empty())
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
) {
519 base::ThreadTaskRunnerHandle::Get()->PostTask(
520 FROM_HERE
, base::Bind(&SpellCheck::PerformSpellCheck
, AsWeakPtr(),
521 base::Owned(request
)));
525 // Mac and Android use their platform engines instead.
526 #if !defined(USE_BROWSER_SPELLCHECKER)
527 void SpellCheck::PerformSpellCheck(SpellcheckRequest
* 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();
537 WebVector
<blink::WebTextCheckingResult
> results
;
538 SpellCheckParagraph(param
->text(), &results
);
539 param
->completion()->didFinishCheckingText(results
);
544 void SpellCheck::CreateTextCheckingResults(
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
),
556 DCHECK_LE(static_cast<size_t>(spellcheck_result
.location
+
557 spellcheck_result
.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())) {
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
)
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
,
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())
607 version_info::Channel channel
= chrome::GetChannel();
608 if (channel
== version_info::Channel::DEV
||
609 channel
== version_info::Channel::CANARY
) {
611 } else if (!base::CommandLine::ForCurrentProcess()->HasSwitch(
612 switches::kEnableAndroidSpellChecker
)) {
616 return spellcheck_enabled_
;