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/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
;
39 const int kNoOffset
= 0;
42 class UpdateSpellcheckEnabled
: public content::RenderViewVisitor
{
44 explicit UpdateSpellcheckEnabled(bool enabled
) : enabled_(enabled
) {}
45 bool Visit(content::RenderView
* render_view
) override
;
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
);
55 provider
->EnableSpellcheck(enabled_
);
59 class DocumentMarkersCollector
: public content::RenderViewVisitor
{
61 DocumentMarkersCollector() {}
62 ~DocumentMarkersCollector() override
{}
63 const std::vector
<uint32
>& markers() const { return markers_
; }
64 bool Visit(content::RenderView
* render_view
) override
;
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())
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.
82 class DocumentMarkersRemover
: public content::RenderViewVisitor
{
84 explicit DocumentMarkersRemover(const std::set
<std::string
>& words
);
85 ~DocumentMarkersRemover() override
{}
86 bool Visit(content::RenderView
* render_view
) override
;
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_
);
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())
131 class SpellCheck::SpellcheckRequest
{
133 SpellcheckRequest(const base::string16
& text
,
134 blink::WebTextCheckingCompletion
* completion
)
135 : text_(text
), completion_(completion
) {
138 ~SpellcheckRequest() {}
140 base::string16
text() { return text_
; }
141 blink::WebTextCheckingCompletion
* completion() { return completion_
; }
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
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())
201 bool SpellCheck::OnControlMessageReceived(const IPC::Message
& message
) {
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()
218 void SpellCheck::OnInit(
219 const std::vector
<SpellCheckBDictLanguage
>& bdict_languages
,
220 const std::set
<std::string
>& custom_words
,
221 bool auto_spell_correct
) {
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());
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())
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
,
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())
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
295 std::vector
<std::vector
<base::string16
>> suggestions_list
;
296 // A vector to hold a language's misspelling suggestions between spellcheck
298 std::vector
<base::string16
> language_suggestions
;
300 // This loop only advances if all languages agree that a sequence of text is
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
=
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);
322 case SpellcheckLanguage::SpellcheckWordResult::IS_CORRECT
:
323 *misspelling_start
= 0;
324 *misspelling_len
= 0;
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();
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
;
352 suggestions_list
.push_back(language_suggestions
);
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
);
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
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(),
397 results
->assign(textcheck_results
);
401 if (!custom_dictionary_
.SpellCheckWord(
402 text
, misspelling_start
, misspelling_length
)) {
403 base::string16 replacement
;
404 textcheck_results
.push_back(WebTextCheckingResult(
405 blink::WebTextDecorationTypeSpelling
,
410 position_in_text
= misspelling_start
+ misspelling_length
;
412 results
->assign(textcheck_results
);
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.
422 base::string16
SpellCheck::GetAutoCorrectionWord(const base::string16
& word
,
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
;
441 if (i
>= word_length
)
442 misspelled_word
[i
] = 0;
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
++) {
451 std::swap(misspelled_word
[i
], misspelled_word
[i
+ 1]);
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
);
464 autocorrect_word
.clear();
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(
486 // We will check this text after we finish loading the hunspell dictionary.
487 if (InitializeIfNeeded())
490 PostDelayedSpellCheckTask(pending_request_param_
.release());
494 bool SpellCheck::InitializeIfNeeded() {
495 if (languages_
.empty())
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
) {
511 base::ThreadTaskRunnerHandle::Get()->PostTask(
512 FROM_HERE
, base::Bind(&SpellCheck::PerformSpellCheck
, AsWeakPtr(),
513 base::Owned(request
)));
517 // Mac and Android use their platform engines instead.
518 #if !defined(USE_BROWSER_SPELLCHECKER)
519 void SpellCheck::PerformSpellCheck(SpellcheckRequest
* 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();
529 WebVector
<blink::WebTextCheckingResult
> results
;
530 SpellCheckParagraph(param
->text(), &results
);
531 param
->completion()->didFinishCheckingText(results
);
536 void SpellCheck::CreateTextCheckingResults(
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
),
548 DCHECK_LE(static_cast<size_t>(spellcheck_result
.location
+
549 spellcheck_result
.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())) {
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
)
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
,
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
)) {
601 return spellcheck_enabled_
;