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/location.h"
12 #include "base/logging.h"
13 #include "base/single_thread_task_runner.h"
14 #include "base/thread_task_runner_handle.h"
15 #include "chrome/common/spellcheck_common.h"
16 #include "chrome/common/spellcheck_messages.h"
17 #include "chrome/common/spellcheck_result.h"
18 #include "chrome/renderer/spellchecker/spellcheck_language.h"
19 #include "chrome/renderer/spellchecker/spellcheck_provider.h"
20 #include "content/public/renderer/render_thread.h"
21 #include "content/public/renderer/render_view.h"
22 #include "content/public/renderer/render_view_visitor.h"
23 #include "third_party/WebKit/public/platform/WebString.h"
24 #include "third_party/WebKit/public/platform/WebVector.h"
25 #include "third_party/WebKit/public/web/WebTextCheckingCompletion.h"
26 #include "third_party/WebKit/public/web/WebTextCheckingResult.h"
27 #include "third_party/WebKit/public/web/WebTextDecorationType.h"
28 #include "third_party/WebKit/public/web/WebView.h"
30 using blink::WebVector
;
31 using blink::WebString
;
32 using blink::WebTextCheckingResult
;
33 using blink::WebTextDecorationType
;
37 class UpdateSpellcheckEnabled
: public content::RenderViewVisitor
{
39 explicit UpdateSpellcheckEnabled(bool enabled
) : enabled_(enabled
) {}
40 bool Visit(content::RenderView
* render_view
) override
;
43 bool enabled_
; // New spellcheck-enabled state.
44 DISALLOW_COPY_AND_ASSIGN(UpdateSpellcheckEnabled
);
47 bool UpdateSpellcheckEnabled::Visit(content::RenderView
* render_view
) {
48 SpellCheckProvider
* provider
= SpellCheckProvider::Get(render_view
);
50 provider
->EnableSpellcheck(enabled_
);
54 class DocumentMarkersCollector
: public content::RenderViewVisitor
{
56 DocumentMarkersCollector() {}
57 ~DocumentMarkersCollector() override
{}
58 const std::vector
<uint32
>& markers() const { return markers_
; }
59 bool Visit(content::RenderView
* render_view
) override
;
62 std::vector
<uint32
> markers_
;
63 DISALLOW_COPY_AND_ASSIGN(DocumentMarkersCollector
);
66 bool DocumentMarkersCollector::Visit(content::RenderView
* render_view
) {
67 if (!render_view
|| !render_view
->GetWebView())
69 WebVector
<uint32
> markers
;
70 render_view
->GetWebView()->spellingMarkers(&markers
);
71 for (size_t i
= 0; i
< markers
.size(); ++i
)
72 markers_
.push_back(markers
[i
]);
73 // Visit all render views.
77 class DocumentMarkersRemover
: public content::RenderViewVisitor
{
79 explicit DocumentMarkersRemover(const std::set
<std::string
>& words
);
80 ~DocumentMarkersRemover() override
{}
81 bool Visit(content::RenderView
* render_view
) override
;
84 WebVector
<WebString
> words_
;
85 DISALLOW_COPY_AND_ASSIGN(DocumentMarkersRemover
);
88 DocumentMarkersRemover::DocumentMarkersRemover(
89 const std::set
<std::string
>& words
)
90 : words_(words
.size()) {
91 std::transform(words
.begin(), words
.end(), words_
.begin(),
92 [](const std::string
& w
) { return WebString::fromUTF8(w
); });
95 bool DocumentMarkersRemover::Visit(content::RenderView
* render_view
) {
96 if (render_view
&& render_view
->GetWebView())
97 render_view
->GetWebView()->removeSpellingMarkersUnderWords(words_
);
101 bool IsApostrophe(base::char16 c
) {
102 const base::char16 kApostrophe
= 0x27;
103 const base::char16 kRightSingleQuotationMark
= 0x2019;
104 return c
== kApostrophe
|| c
== kRightSingleQuotationMark
;
107 // Makes sure that the apostrophes in the |spelling_suggestion| are the same
108 // type as in the |misspelled_word| and in the same order. Ignore differences in
109 // the number of apostrophes.
110 void PreserveOriginalApostropheTypes(const base::string16
& misspelled_word
,
111 base::string16
* spelling_suggestion
) {
112 auto it
= spelling_suggestion
->begin();
113 for (const base::char16
& c
: misspelled_word
) {
114 if (IsApostrophe(c
)) {
115 it
= std::find_if(it
, spelling_suggestion
->end(), IsApostrophe
);
116 if (it
== spelling_suggestion
->end())
126 class SpellCheck::SpellcheckRequest
{
128 SpellcheckRequest(const base::string16
& text
,
129 blink::WebTextCheckingCompletion
* completion
)
130 : text_(text
), completion_(completion
) {
133 ~SpellcheckRequest() {}
135 base::string16
text() { return text_
; }
136 blink::WebTextCheckingCompletion
* completion() { return completion_
; }
139 base::string16 text_
; // Text to be checked in this task.
141 // The interface to send the misspelled ranges to WebKit.
142 blink::WebTextCheckingCompletion
* completion_
;
144 DISALLOW_COPY_AND_ASSIGN(SpellcheckRequest
);
148 // Initializes SpellCheck object.
149 // spellcheck_enabled_ currently MUST be set to true, due to peculiarities of
150 // the initialization sequence.
151 // Since it defaults to true, newly created SpellCheckProviders will enable
152 // spellchecking. After the first word is typed, the provider requests a check,
153 // which in turn triggers the delayed initialization sequence in SpellCheck.
154 // This does send a message to the browser side, which triggers the creation
155 // of the SpellcheckService. That does create the observer for the preference
156 // responsible for enabling/disabling checking, which allows subsequent changes
157 // to that preference to be sent to all SpellCheckProviders.
158 // Setting |spellcheck_enabled_| to false by default prevents that mechanism,
159 // and as such the SpellCheckProviders will never be notified of different
161 // TODO(groby): Simplify this.
162 SpellCheck::SpellCheck()
163 : auto_spell_correct_turned_on_(false),
164 spellcheck_enabled_(true) {
165 languages_
.push_back(new SpellcheckLanguage());
168 SpellCheck::~SpellCheck() {
171 bool SpellCheck::OnControlMessageReceived(const IPC::Message
& message
) {
173 IPC_BEGIN_MESSAGE_MAP(SpellCheck
, message
)
174 IPC_MESSAGE_HANDLER(SpellCheckMsg_Init
, OnInit
)
175 IPC_MESSAGE_HANDLER(SpellCheckMsg_CustomDictionaryChanged
,
176 OnCustomDictionaryChanged
)
177 IPC_MESSAGE_HANDLER(SpellCheckMsg_EnableAutoSpellCorrect
,
178 OnEnableAutoSpellCorrect
)
179 IPC_MESSAGE_HANDLER(SpellCheckMsg_EnableSpellCheck
, OnEnableSpellCheck
)
180 IPC_MESSAGE_HANDLER(SpellCheckMsg_RequestDocumentMarkers
,
181 OnRequestDocumentMarkers
)
182 IPC_MESSAGE_UNHANDLED(handled
= false)
183 IPC_END_MESSAGE_MAP()
188 void SpellCheck::OnInit(IPC::PlatformFileForTransit bdict_file
,
189 const std::set
<std::string
>& custom_words
,
190 const std::string
& language
,
191 bool auto_spell_correct
) {
192 Init(IPC::PlatformFileForTransitToFile(bdict_file
), custom_words
, language
);
193 auto_spell_correct_turned_on_
= auto_spell_correct
;
194 #if !defined(OS_MACOSX)
195 PostDelayedSpellCheckTask(pending_request_param_
.release());
199 void SpellCheck::OnCustomDictionaryChanged(
200 const std::set
<std::string
>& words_added
,
201 const std::set
<std::string
>& words_removed
) {
202 custom_dictionary_
.OnCustomDictionaryChanged(words_added
, words_removed
);
203 if (words_added
.empty())
205 DocumentMarkersRemover
markersRemover(words_added
);
206 content::RenderView::ForEach(&markersRemover
);
209 void SpellCheck::OnEnableAutoSpellCorrect(bool enable
) {
210 auto_spell_correct_turned_on_
= enable
;
213 void SpellCheck::OnEnableSpellCheck(bool enable
) {
214 spellcheck_enabled_
= enable
;
215 UpdateSpellcheckEnabled
updater(enable
);
216 content::RenderView::ForEach(&updater
);
219 void SpellCheck::OnRequestDocumentMarkers() {
220 DocumentMarkersCollector collector
;
221 content::RenderView::ForEach(&collector
);
222 content::RenderThread::Get()->Send(
223 new SpellCheckHostMsg_RespondDocumentMarkers(collector
.markers()));
226 // TODO(groby): Make sure we always have a spelling engine, even before Init()
228 void SpellCheck::Init(base::File file
,
229 const std::set
<std::string
>& custom_words
,
230 const std::string
& language
) {
231 languages_
.front()->Init(file
.Pass(), language
);
232 custom_dictionary_
.Init(custom_words
);
235 bool SpellCheck::SpellCheckWord(
236 const base::char16
* in_word
,
239 int* misspelling_start
,
240 int* misspelling_len
,
241 std::vector
<base::string16
>* optional_suggestions
) {
242 DCHECK(in_word_len
>= 0);
243 DCHECK(misspelling_start
&& misspelling_len
) << "Out vars must be given.";
245 // Do nothing if we need to delay initialization. (Rather than blocking,
246 // report the word as correctly spelled.)
247 if (InitializeIfNeeded())
250 return languages_
.front()->SpellCheckWord(in_word
, in_word_len
, tag
,
251 misspelling_start
, misspelling_len
,
252 optional_suggestions
);
255 bool SpellCheck::SpellCheckParagraph(
256 const base::string16
& text
,
257 WebVector
<WebTextCheckingResult
>* results
) {
258 #if !defined(OS_MACOSX)
259 // Mac has its own spell checker, so this method will not be used.
261 std::vector
<WebTextCheckingResult
> textcheck_results
;
262 size_t length
= text
.length();
265 // Spellcheck::SpellCheckWord() automatically breaks text into words and
266 // checks the spellings of the extracted words. This function sets the
267 // position and length of the first misspelled word and returns false when
268 // the text includes misspelled words. Therefore, we just repeat calling the
269 // function until it returns true to check the whole text.
270 int misspelling_start
= 0;
271 int misspelling_length
= 0;
272 while (offset
<= length
) {
273 if (SpellCheckWord(&text
[offset
],
279 results
->assign(textcheck_results
);
283 if (!custom_dictionary_
.SpellCheckWord(
284 text
, misspelling_start
+ offset
, misspelling_length
)) {
285 base::string16 replacement
;
286 textcheck_results
.push_back(WebTextCheckingResult(
287 blink::WebTextDecorationTypeSpelling
,
288 misspelling_start
+ offset
,
292 offset
+= misspelling_start
+ misspelling_length
;
294 results
->assign(textcheck_results
);
297 // This function is only invoked for spell checker functionality that runs
298 // on the render thread. OSX builds don't have that.
304 base::string16
SpellCheck::GetAutoCorrectionWord(const base::string16
& word
,
306 base::string16 autocorrect_word
;
307 if (!auto_spell_correct_turned_on_
)
308 return autocorrect_word
; // Return the empty string.
310 int word_length
= static_cast<int>(word
.size());
311 if (word_length
< 2 ||
312 word_length
> chrome::spellcheck_common::kMaxAutoCorrectWordSize
)
313 return autocorrect_word
;
315 if (InitializeIfNeeded())
316 return autocorrect_word
;
318 base::char16 misspelled_word
[
319 chrome::spellcheck_common::kMaxAutoCorrectWordSize
+ 1];
320 const base::char16
* word_char
= word
.c_str();
321 for (int i
= 0; i
<= chrome::spellcheck_common::kMaxAutoCorrectWordSize
;
323 if (i
>= word_length
)
324 misspelled_word
[i
] = 0;
326 misspelled_word
[i
] = word_char
[i
];
329 // Swap adjacent characters and spellcheck.
330 int misspelling_start
, misspelling_len
;
331 for (int i
= 0; i
< word_length
- 1; i
++) {
333 std::swap(misspelled_word
[i
], misspelled_word
[i
+ 1]);
336 misspelling_start
= misspelling_len
= 0;
337 SpellCheckWord(misspelled_word
, word_length
, tag
, &misspelling_start
,
338 &misspelling_len
, NULL
);
340 // Make decision: if only one swap produced a valid word, then we want to
341 // return it. If we found two or more, we don't do autocorrection.
342 if (misspelling_len
== 0) {
343 if (autocorrect_word
.empty()) {
344 autocorrect_word
.assign(misspelled_word
);
346 autocorrect_word
.clear();
351 // Restore the swapped characters.
352 std::swap(misspelled_word
[i
], misspelled_word
[i
+ 1]);
354 return autocorrect_word
;
357 #if !defined(OS_MACOSX) // OSX uses its own spell checker
358 void SpellCheck::RequestTextChecking(
359 const base::string16
& text
,
360 blink::WebTextCheckingCompletion
* completion
) {
361 // Clean up the previous request before starting a new request.
362 if (pending_request_param_
.get())
363 pending_request_param_
->completion()->didCancelCheckingText();
365 pending_request_param_
.reset(new SpellcheckRequest(
367 // We will check this text after we finish loading the hunspell dictionary.
368 if (InitializeIfNeeded())
371 PostDelayedSpellCheckTask(pending_request_param_
.release());
375 bool SpellCheck::InitializeIfNeeded() {
376 return languages_
.front()->InitializeIfNeeded();
379 #if !defined(OS_MACOSX) // OSX doesn't have |pending_request_param_|
380 void SpellCheck::PostDelayedSpellCheckTask(SpellcheckRequest
* request
) {
384 base::ThreadTaskRunnerHandle::Get()->PostTask(
385 FROM_HERE
, base::Bind(&SpellCheck::PerformSpellCheck
, AsWeakPtr(),
386 base::Owned(request
)));
390 #if !defined(OS_MACOSX) // Mac uses its platform engine instead.
391 void SpellCheck::PerformSpellCheck(SpellcheckRequest
* param
) {
394 if (!languages_
.front()->IsEnabled()) {
395 param
->completion()->didCancelCheckingText();
397 WebVector
<blink::WebTextCheckingResult
> results
;
398 SpellCheckParagraph(param
->text(), &results
);
399 param
->completion()->didFinishCheckingText(results
);
404 void SpellCheck::CreateTextCheckingResults(
407 const base::string16
& line_text
,
408 const std::vector
<SpellCheckResult
>& spellcheck_results
,
409 WebVector
<WebTextCheckingResult
>* textcheck_results
) {
410 DCHECK(!line_text
.empty());
412 std::vector
<WebTextCheckingResult
> results
;
413 for (const SpellCheckResult
& spellcheck_result
: spellcheck_results
) {
414 DCHECK_LE(static_cast<size_t>(spellcheck_result
.location
),
416 DCHECK_LE(static_cast<size_t>(spellcheck_result
.location
+
417 spellcheck_result
.length
),
420 const base::string16
& misspelled_word
=
421 line_text
.substr(spellcheck_result
.location
, spellcheck_result
.length
);
422 base::string16 replacement
= spellcheck_result
.replacement
;
423 SpellCheckResult::Decoration decoration
= spellcheck_result
.decoration
;
425 // Ignore words in custom dictionary.
426 if (custom_dictionary_
.SpellCheckWord(misspelled_word
, 0,
427 misspelled_word
.length())) {
431 // Use the same types of appostrophes as in the mispelled word.
432 PreserveOriginalApostropheTypes(misspelled_word
, &replacement
);
434 // Ignore misspellings due the typographical apostrophe.
435 if (misspelled_word
== replacement
)
438 if (filter
== USE_NATIVE_CHECKER
) {
439 // Double-check misspelled words with out spellchecker and attach grammar
440 // markers to them if our spellchecker tells us they are correct words,
441 // i.e. they are probably contextually-misspelled words.
442 int unused_misspelling_start
= 0;
443 int unused_misspelling_length
= 0;
444 if (decoration
== SpellCheckResult::SPELLING
&&
445 SpellCheckWord(misspelled_word
.c_str(), misspelled_word
.length(), 0,
446 &unused_misspelling_start
, &unused_misspelling_length
,
448 decoration
= SpellCheckResult::GRAMMAR
;
452 results
.push_back(WebTextCheckingResult(
453 static_cast<WebTextDecorationType
>(decoration
),
454 line_offset
+ spellcheck_result
.location
, spellcheck_result
.length
,
455 replacement
, spellcheck_result
.hash
));
458 textcheck_results
->assign(results
);