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 // 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())
204 bool SpellCheck::OnControlMessageReceived(const IPC::Message
& message
) {
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()
221 void SpellCheck::OnInit(
222 const std::vector
<SpellCheckBDictLanguage
>& bdict_languages
,
223 const std::set
<std::string
>& custom_words
,
224 bool auto_spell_correct
) {
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());
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())
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
,
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())
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
298 std::vector
<std::vector
<base::string16
>> suggestions_list
;
299 // A vector to hold a language's misspelling suggestions between spellcheck
301 std::vector
<base::string16
> language_suggestions
;
303 // This loop only advances if all languages agree that a sequence of text is
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
=
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);
325 case SpellcheckLanguage::SpellcheckWordResult::IS_CORRECT
:
326 *misspelling_start
= 0;
327 *misspelling_len
= 0;
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();
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
;
355 suggestions_list
.push_back(language_suggestions
);
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
);
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
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(),
400 results
->assign(textcheck_results
);
404 if (!custom_dictionary_
.SpellCheckWord(
405 text
, misspelling_start
, misspelling_length
)) {
406 base::string16 replacement
;
407 textcheck_results
.push_back(WebTextCheckingResult(
408 blink::WebTextDecorationTypeSpelling
,
413 position_in_text
= misspelling_start
+ misspelling_length
;
415 results
->assign(textcheck_results
);
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.
425 base::string16
SpellCheck::GetAutoCorrectionWord(const base::string16
& word
,
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
;
444 if (i
>= word_length
)
445 misspelled_word
[i
] = 0;
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
++) {
454 std::swap(misspelled_word
[i
], misspelled_word
[i
+ 1]);
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
);
467 autocorrect_word
.clear();
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(
489 // We will check this text after we finish loading the hunspell dictionary.
490 if (InitializeIfNeeded())
493 PostDelayedSpellCheckTask(pending_request_param_
.release());
497 bool SpellCheck::InitializeIfNeeded() {
498 if (languages_
.empty())
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
) {
514 base::ThreadTaskRunnerHandle::Get()->PostTask(
515 FROM_HERE
, base::Bind(&SpellCheck::PerformSpellCheck
, AsWeakPtr(),
516 base::Owned(request
)));
520 // Mac and Android use their platform engines instead.
521 #if !defined(USE_BROWSER_SPELLCHECKER)
522 void SpellCheck::PerformSpellCheck(SpellcheckRequest
* 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();
532 WebVector
<blink::WebTextCheckingResult
> results
;
533 SpellCheckParagraph(param
->text(), &results
);
534 param
->completion()->didFinishCheckingText(results
);
539 void SpellCheck::CreateTextCheckingResults(
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
),
551 DCHECK_LE(static_cast<size_t>(spellcheck_result
.location
+
552 spellcheck_result
.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())) {
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
)
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
,
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())
602 version_info::Channel channel
= chrome::GetChannel();
603 if (channel
== version_info::Channel::DEV
||
604 channel
== version_info::Channel::CANARY
) {
606 } else if (!base::CommandLine::ForCurrentProcess()->HasSwitch(
607 switches::kEnableAndroidSpellChecker
)) {
611 return spellcheck_enabled_
;