OLD | NEW |
| (Empty) |
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. | |
4 | |
5 #include "chrome/renderer/spellchecker/spellcheck.h" | |
6 | |
7 #include <stddef.h> | |
8 #include <stdint.h> | |
9 #include <algorithm> | |
10 #include <utility> | |
11 | |
12 #include "base/bind.h" | |
13 #include "base/command_line.h" | |
14 #include "base/location.h" | |
15 #include "base/logging.h" | |
16 #include "base/macros.h" | |
17 #include "base/single_thread_task_runner.h" | |
18 #include "base/stl_util.h" | |
19 #include "base/threading/thread_task_runner_handle.h" | |
20 #include "build/build_config.h" | |
21 #include "chrome/common/channel_info.h" | |
22 #include "chrome/renderer/spellchecker/spellcheck_language.h" | |
23 #include "chrome/renderer/spellchecker/spellcheck_provider.h" | |
24 #include "components/spellcheck/common/spellcheck_common.h" | |
25 #include "components/spellcheck/common/spellcheck_messages.h" | |
26 #include "components/spellcheck/common/spellcheck_result.h" | |
27 #include "components/spellcheck/common/spellcheck_switches.h" | |
28 #include "content/public/renderer/render_thread.h" | |
29 #include "content/public/renderer/render_view.h" | |
30 #include "content/public/renderer/render_view_visitor.h" | |
31 #include "ipc/ipc_platform_file.h" | |
32 #include "third_party/WebKit/public/platform/WebString.h" | |
33 #include "third_party/WebKit/public/platform/WebVector.h" | |
34 #include "third_party/WebKit/public/web/WebTextCheckingCompletion.h" | |
35 #include "third_party/WebKit/public/web/WebTextCheckingResult.h" | |
36 #include "third_party/WebKit/public/web/WebTextDecorationType.h" | |
37 #include "third_party/WebKit/public/web/WebView.h" | |
38 | |
39 using blink::WebVector; | |
40 using blink::WebString; | |
41 using blink::WebTextCheckingResult; | |
42 using blink::WebTextDecorationType; | |
43 | |
44 namespace { | |
45 const int kNoOffset = 0; | |
46 const int kNoTag = 0; | |
47 | |
48 class UpdateSpellcheckEnabled : public content::RenderViewVisitor { | |
49 public: | |
50 explicit UpdateSpellcheckEnabled(bool enabled) : enabled_(enabled) {} | |
51 bool Visit(content::RenderView* render_view) override; | |
52 | |
53 private: | |
54 bool enabled_; // New spellcheck-enabled state. | |
55 DISALLOW_COPY_AND_ASSIGN(UpdateSpellcheckEnabled); | |
56 }; | |
57 | |
58 bool UpdateSpellcheckEnabled::Visit(content::RenderView* render_view) { | |
59 SpellCheckProvider* provider = SpellCheckProvider::Get(render_view); | |
60 DCHECK(provider); | |
61 provider->EnableSpellcheck(enabled_); | |
62 return true; | |
63 } | |
64 | |
65 class DocumentMarkersCollector : public content::RenderViewVisitor { | |
66 public: | |
67 DocumentMarkersCollector() {} | |
68 ~DocumentMarkersCollector() override {} | |
69 const std::vector<uint32_t>& markers() const { return markers_; } | |
70 bool Visit(content::RenderView* render_view) override; | |
71 | |
72 private: | |
73 std::vector<uint32_t> markers_; | |
74 DISALLOW_COPY_AND_ASSIGN(DocumentMarkersCollector); | |
75 }; | |
76 | |
77 bool DocumentMarkersCollector::Visit(content::RenderView* render_view) { | |
78 if (!render_view || !render_view->GetWebView()) | |
79 return true; | |
80 WebVector<uint32_t> markers; | |
81 render_view->GetWebView()->spellingMarkers(&markers); | |
82 for (size_t i = 0; i < markers.size(); ++i) | |
83 markers_.push_back(markers[i]); | |
84 // Visit all render views. | |
85 return true; | |
86 } | |
87 | |
88 class DocumentMarkersRemover : public content::RenderViewVisitor { | |
89 public: | |
90 explicit DocumentMarkersRemover(const std::set<std::string>& words); | |
91 ~DocumentMarkersRemover() override {} | |
92 bool Visit(content::RenderView* render_view) override; | |
93 | |
94 private: | |
95 WebVector<WebString> words_; | |
96 DISALLOW_COPY_AND_ASSIGN(DocumentMarkersRemover); | |
97 }; | |
98 | |
99 DocumentMarkersRemover::DocumentMarkersRemover( | |
100 const std::set<std::string>& words) | |
101 : words_(words.size()) { | |
102 std::transform(words.begin(), words.end(), words_.begin(), | |
103 [](const std::string& w) { return WebString::fromUTF8(w); }); | |
104 } | |
105 | |
106 bool DocumentMarkersRemover::Visit(content::RenderView* render_view) { | |
107 if (render_view && render_view->GetWebView()) | |
108 render_view->GetWebView()->removeSpellingMarkersUnderWords(words_); | |
109 return true; | |
110 } | |
111 | |
112 bool IsApostrophe(base::char16 c) { | |
113 const base::char16 kApostrophe = 0x27; | |
114 const base::char16 kRightSingleQuotationMark = 0x2019; | |
115 return c == kApostrophe || c == kRightSingleQuotationMark; | |
116 } | |
117 | |
118 // Makes sure that the apostrophes in the |spelling_suggestion| are the same | |
119 // type as in the |misspelled_word| and in the same order. Ignore differences in | |
120 // the number of apostrophes. | |
121 void PreserveOriginalApostropheTypes(const base::string16& misspelled_word, | |
122 base::string16* spelling_suggestion) { | |
123 auto it = spelling_suggestion->begin(); | |
124 for (const base::char16& c : misspelled_word) { | |
125 if (IsApostrophe(c)) { | |
126 it = std::find_if(it, spelling_suggestion->end(), IsApostrophe); | |
127 if (it == spelling_suggestion->end()) | |
128 return; | |
129 | |
130 *it++ = c; | |
131 } | |
132 } | |
133 } | |
134 | |
135 } // namespace | |
136 | |
137 class SpellCheck::SpellcheckRequest { | |
138 public: | |
139 SpellcheckRequest(const base::string16& text, | |
140 blink::WebTextCheckingCompletion* completion) | |
141 : text_(text), completion_(completion) { | |
142 DCHECK(completion); | |
143 } | |
144 ~SpellcheckRequest() {} | |
145 | |
146 base::string16 text() { return text_; } | |
147 blink::WebTextCheckingCompletion* completion() { return completion_; } | |
148 | |
149 private: | |
150 base::string16 text_; // Text to be checked in this task. | |
151 | |
152 // The interface to send the misspelled ranges to WebKit. | |
153 blink::WebTextCheckingCompletion* completion_; | |
154 | |
155 DISALLOW_COPY_AND_ASSIGN(SpellcheckRequest); | |
156 }; | |
157 | |
158 | |
159 // Initializes SpellCheck object. | |
160 // spellcheck_enabled_ currently MUST be set to true, due to peculiarities of | |
161 // the initialization sequence. | |
162 // Since it defaults to true, newly created SpellCheckProviders will enable | |
163 // spellchecking. After the first word is typed, the provider requests a check, | |
164 // which in turn triggers the delayed initialization sequence in SpellCheck. | |
165 // This does send a message to the browser side, which triggers the creation | |
166 // of the SpellcheckService. That does create the observer for the preference | |
167 // responsible for enabling/disabling checking, which allows subsequent changes | |
168 // to that preference to be sent to all SpellCheckProviders. | |
169 // Setting |spellcheck_enabled_| to false by default prevents that mechanism, | |
170 // and as such the SpellCheckProviders will never be notified of different | |
171 // values. | |
172 // TODO(groby): Simplify this. | |
173 SpellCheck::SpellCheck() : spellcheck_enabled_(true) {} | |
174 | |
175 SpellCheck::~SpellCheck() { | |
176 } | |
177 | |
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(); | |
183 | |
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()); | |
188 | |
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; | |
192 | |
193 if (suggestions_list[language].size() <= index) | |
194 continue; | |
195 | |
196 const base::string16& suggestion = suggestions_list[language][index]; | |
197 // Only add the suggestion if it's unique. | |
198 if (!ContainsValue(*optional_suggestions, suggestion)) { | |
199 optional_suggestions->push_back(suggestion); | |
200 } | |
201 if (optional_suggestions->size() >= spellcheck::kMaxSuggestions) { | |
202 break; | |
203 } | |
204 } | |
205 } | |
206 | |
207 bool SpellCheck::OnControlMessageReceived(const IPC::Message& message) { | |
208 bool handled = true; | |
209 IPC_BEGIN_MESSAGE_MAP(SpellCheck, message) | |
210 IPC_MESSAGE_HANDLER(SpellCheckMsg_Init, OnInit) | |
211 IPC_MESSAGE_HANDLER(SpellCheckMsg_CustomDictionaryChanged, | |
212 OnCustomDictionaryChanged) | |
213 IPC_MESSAGE_HANDLER(SpellCheckMsg_EnableSpellCheck, OnEnableSpellCheck) | |
214 IPC_MESSAGE_HANDLER(SpellCheckMsg_RequestDocumentMarkers, | |
215 OnRequestDocumentMarkers) | |
216 IPC_MESSAGE_UNHANDLED(handled = false) | |
217 IPC_END_MESSAGE_MAP() | |
218 | |
219 return handled; | |
220 } | |
221 | |
222 void SpellCheck::OnInit( | |
223 const std::vector<SpellCheckBDictLanguage>& bdict_languages, | |
224 const std::set<std::string>& custom_words) { | |
225 languages_.clear(); | |
226 for (const auto& bdict_language : bdict_languages) { | |
227 AddSpellcheckLanguage( | |
228 IPC::PlatformFileForTransitToFile(bdict_language.file), | |
229 bdict_language.language); | |
230 } | |
231 | |
232 custom_dictionary_.Init(custom_words); | |
233 #if !defined(USE_BROWSER_SPELLCHECKER) | |
234 PostDelayedSpellCheckTask(pending_request_param_.release()); | |
235 #endif | |
236 } | |
237 | |
238 void SpellCheck::OnCustomDictionaryChanged( | |
239 const std::set<std::string>& words_added, | |
240 const std::set<std::string>& words_removed) { | |
241 custom_dictionary_.OnCustomDictionaryChanged(words_added, words_removed); | |
242 if (words_added.empty()) | |
243 return; | |
244 DocumentMarkersRemover markersRemover(words_added); | |
245 content::RenderView::ForEach(&markersRemover); | |
246 } | |
247 | |
248 void SpellCheck::OnEnableSpellCheck(bool enable) { | |
249 spellcheck_enabled_ = enable; | |
250 UpdateSpellcheckEnabled updater(enable); | |
251 content::RenderView::ForEach(&updater); | |
252 } | |
253 | |
254 void SpellCheck::OnRequestDocumentMarkers() { | |
255 DocumentMarkersCollector collector; | |
256 content::RenderView::ForEach(&collector); | |
257 content::RenderThread::Get()->Send( | |
258 new SpellCheckHostMsg_RespondDocumentMarkers(collector.markers())); | |
259 } | |
260 | |
261 // TODO(groby): Make sure we always have a spelling engine, even before | |
262 // AddSpellcheckLanguage() is called. | |
263 void SpellCheck::AddSpellcheckLanguage(base::File file, | |
264 const std::string& language) { | |
265 languages_.push_back(new SpellcheckLanguage()); | |
266 languages_.back()->Init(std::move(file), language); | |
267 } | |
268 | |
269 bool SpellCheck::SpellCheckWord( | |
270 const base::char16* text_begin, | |
271 int position_in_text, | |
272 int text_length, | |
273 int tag, | |
274 int* misspelling_start, | |
275 int* misspelling_len, | |
276 std::vector<base::string16>* optional_suggestions) { | |
277 DCHECK(text_length >= position_in_text); | |
278 DCHECK(misspelling_start && misspelling_len) << "Out vars must be given."; | |
279 | |
280 // Do nothing if we need to delay initialization. (Rather than blocking, | |
281 // report the word as correctly spelled.) | |
282 if (InitializeIfNeeded()) | |
283 return true; | |
284 | |
285 // These are for holding misspelling or skippable word positions and lengths | |
286 // between calls to SpellcheckLanguage::SpellCheckWord. | |
287 int possible_misspelling_start; | |
288 int possible_misspelling_len; | |
289 // The longest sequence of text that all languages agree is skippable. | |
290 int agreed_skippable_len; | |
291 // A vector of vectors containing spelling suggestions from different | |
292 // languages. | |
293 std::vector<std::vector<base::string16>> suggestions_list; | |
294 // A vector to hold a language's misspelling suggestions between spellcheck | |
295 // calls. | |
296 std::vector<base::string16> language_suggestions; | |
297 | |
298 // This loop only advances if all languages agree that a sequence of text is | |
299 // skippable. | |
300 for (; position_in_text <= text_length; | |
301 position_in_text += agreed_skippable_len) { | |
302 // Reseting |agreed_skippable_len| to the worst-case length each time | |
303 // prevents some unnecessary iterations. | |
304 agreed_skippable_len = text_length; | |
305 *misspelling_start = 0; | |
306 *misspelling_len = 0; | |
307 suggestions_list.clear(); | |
308 | |
309 for (ScopedVector<SpellcheckLanguage>::iterator language = | |
310 languages_.begin(); | |
311 language != languages_.end();) { | |
312 language_suggestions.clear(); | |
313 SpellcheckLanguage::SpellcheckWordResult result = | |
314 (*language)->SpellCheckWord( | |
315 text_begin, position_in_text, text_length, tag, | |
316 &possible_misspelling_start, &possible_misspelling_len, | |
317 optional_suggestions ? &language_suggestions : nullptr); | |
318 | |
319 switch (result) { | |
320 case SpellcheckLanguage::SpellcheckWordResult::IS_CORRECT: | |
321 *misspelling_start = 0; | |
322 *misspelling_len = 0; | |
323 return true; | |
324 case SpellcheckLanguage::SpellcheckWordResult::IS_SKIPPABLE: | |
325 agreed_skippable_len = | |
326 std::min(agreed_skippable_len, possible_misspelling_len); | |
327 // If true, this means the spellchecker moved past a word that was | |
328 // previously determined to be misspelled or skippable, which means | |
329 // another spellcheck language marked it as correct. | |
330 if (position_in_text != possible_misspelling_start) { | |
331 *misspelling_len = 0; | |
332 position_in_text = possible_misspelling_start; | |
333 suggestions_list.clear(); | |
334 language = languages_.begin(); | |
335 } else { | |
336 language++; | |
337 } | |
338 break; | |
339 case SpellcheckLanguage::SpellcheckWordResult::IS_MISSPELLED: | |
340 *misspelling_start = possible_misspelling_start; | |
341 *misspelling_len = possible_misspelling_len; | |
342 // If true, this means the spellchecker moved past a word that was | |
343 // previously determined to be misspelled or skippable, which means | |
344 // another spellcheck language marked it as correct. | |
345 if (position_in_text != *misspelling_start) { | |
346 suggestions_list.clear(); | |
347 language = languages_.begin(); | |
348 position_in_text = *misspelling_start; | |
349 } else { | |
350 suggestions_list.push_back(language_suggestions); | |
351 language++; | |
352 } | |
353 break; | |
354 } | |
355 } | |
356 | |
357 // If |*misspelling_len| is non-zero, that means at least one language | |
358 // marked a word misspelled and no other language considered it correct. | |
359 if (*misspelling_len != 0) { | |
360 if (optional_suggestions) | |
361 FillSuggestions(suggestions_list, optional_suggestions); | |
362 return false; | |
363 } | |
364 } | |
365 | |
366 NOTREACHED(); | |
367 return true; | |
368 } | |
369 | |
370 bool SpellCheck::SpellCheckParagraph( | |
371 const base::string16& text, | |
372 WebVector<WebTextCheckingResult>* results) { | |
373 #if !defined(USE_BROWSER_SPELLCHECKER) | |
374 // Mac and Android have their own spell checkers,so this method won't be used | |
375 DCHECK(results); | |
376 std::vector<WebTextCheckingResult> textcheck_results; | |
377 size_t length = text.length(); | |
378 size_t position_in_text = 0; | |
379 | |
380 // Spellcheck::SpellCheckWord() automatically breaks text into words and | |
381 // checks the spellings of the extracted words. This function sets the | |
382 // position and length of the first misspelled word and returns false when | |
383 // the text includes misspelled words. Therefore, we just repeat calling the | |
384 // function until it returns true to check the whole text. | |
385 int misspelling_start = 0; | |
386 int misspelling_length = 0; | |
387 while (position_in_text <= length) { | |
388 if (SpellCheckWord(text.c_str(), | |
389 position_in_text, | |
390 length, | |
391 kNoTag, | |
392 &misspelling_start, | |
393 &misspelling_length, | |
394 NULL)) { | |
395 results->assign(textcheck_results); | |
396 return true; | |
397 } | |
398 | |
399 if (!custom_dictionary_.SpellCheckWord( | |
400 text, misspelling_start, misspelling_length)) { | |
401 base::string16 replacement; | |
402 textcheck_results.push_back(WebTextCheckingResult( | |
403 blink::WebTextDecorationTypeSpelling, | |
404 misspelling_start, | |
405 misspelling_length, | |
406 replacement)); | |
407 } | |
408 position_in_text = misspelling_start + misspelling_length; | |
409 } | |
410 results->assign(textcheck_results); | |
411 return false; | |
412 #else | |
413 // This function is only invoked for spell checker functionality that runs | |
414 // on the render thread. OSX and Android builds don't have that. | |
415 NOTREACHED(); | |
416 return true; | |
417 #endif | |
418 } | |
419 | |
420 // OSX and Android use their own spell checkers | |
421 #if !defined(USE_BROWSER_SPELLCHECKER) | |
422 void SpellCheck::RequestTextChecking( | |
423 const base::string16& text, | |
424 blink::WebTextCheckingCompletion* completion) { | |
425 // Clean up the previous request before starting a new request. | |
426 if (pending_request_param_.get()) | |
427 pending_request_param_->completion()->didCancelCheckingText(); | |
428 | |
429 pending_request_param_.reset(new SpellcheckRequest( | |
430 text, completion)); | |
431 // We will check this text after we finish loading the hunspell dictionary. | |
432 if (InitializeIfNeeded()) | |
433 return; | |
434 | |
435 PostDelayedSpellCheckTask(pending_request_param_.release()); | |
436 } | |
437 #endif | |
438 | |
439 bool SpellCheck::InitializeIfNeeded() { | |
440 if (languages_.empty()) | |
441 return true; | |
442 | |
443 bool initialize_if_needed = false; | |
444 for (SpellcheckLanguage* language : languages_) | |
445 initialize_if_needed |= language->InitializeIfNeeded(); | |
446 | |
447 return initialize_if_needed; | |
448 } | |
449 | |
450 // OSX and Android don't have |pending_request_param_| | |
451 #if !defined(USE_BROWSER_SPELLCHECKER) | |
452 void SpellCheck::PostDelayedSpellCheckTask(SpellcheckRequest* request) { | |
453 if (!request) | |
454 return; | |
455 | |
456 base::ThreadTaskRunnerHandle::Get()->PostTask( | |
457 FROM_HERE, base::Bind(&SpellCheck::PerformSpellCheck, AsWeakPtr(), | |
458 base::Owned(request))); | |
459 } | |
460 #endif | |
461 | |
462 // Mac and Android use their platform engines instead. | |
463 #if !defined(USE_BROWSER_SPELLCHECKER) | |
464 void SpellCheck::PerformSpellCheck(SpellcheckRequest* param) { | |
465 DCHECK(param); | |
466 | |
467 if (languages_.empty() || | |
468 std::find_if(languages_.begin(), languages_.end(), | |
469 [](SpellcheckLanguage* language) { | |
470 return !language->IsEnabled(); | |
471 }) != languages_.end()) { | |
472 param->completion()->didCancelCheckingText(); | |
473 } else { | |
474 WebVector<blink::WebTextCheckingResult> results; | |
475 SpellCheckParagraph(param->text(), &results); | |
476 param->completion()->didFinishCheckingText(results); | |
477 } | |
478 } | |
479 #endif | |
480 | |
481 void SpellCheck::CreateTextCheckingResults( | |
482 ResultFilter filter, | |
483 int line_offset, | |
484 const base::string16& line_text, | |
485 const std::vector<SpellCheckResult>& spellcheck_results, | |
486 WebVector<WebTextCheckingResult>* textcheck_results) { | |
487 DCHECK(!line_text.empty()); | |
488 | |
489 std::vector<WebTextCheckingResult> results; | |
490 for (const SpellCheckResult& spellcheck_result : spellcheck_results) { | |
491 DCHECK_LE(static_cast<size_t>(spellcheck_result.location), | |
492 line_text.length()); | |
493 DCHECK_LE(static_cast<size_t>(spellcheck_result.location + | |
494 spellcheck_result.length), | |
495 line_text.length()); | |
496 | |
497 const base::string16& misspelled_word = | |
498 line_text.substr(spellcheck_result.location, spellcheck_result.length); | |
499 base::string16 replacement = spellcheck_result.replacement; | |
500 SpellCheckResult::Decoration decoration = spellcheck_result.decoration; | |
501 | |
502 // Ignore words in custom dictionary. | |
503 if (custom_dictionary_.SpellCheckWord(misspelled_word, 0, | |
504 misspelled_word.length())) { | |
505 continue; | |
506 } | |
507 | |
508 // Use the same types of appostrophes as in the mispelled word. | |
509 PreserveOriginalApostropheTypes(misspelled_word, &replacement); | |
510 | |
511 // Ignore misspellings due the typographical apostrophe. | |
512 if (misspelled_word == replacement) | |
513 continue; | |
514 | |
515 if (filter == USE_NATIVE_CHECKER) { | |
516 // Double-check misspelled words with out spellchecker and attach grammar | |
517 // markers to them if our spellchecker tells us they are correct words, | |
518 // i.e. they are probably contextually-misspelled words. | |
519 int unused_misspelling_start = 0; | |
520 int unused_misspelling_length = 0; | |
521 if (decoration == SpellCheckResult::SPELLING && | |
522 SpellCheckWord(misspelled_word.c_str(), kNoOffset, | |
523 misspelled_word.length(), kNoTag, | |
524 &unused_misspelling_start, &unused_misspelling_length, | |
525 nullptr)) { | |
526 decoration = SpellCheckResult::GRAMMAR; | |
527 } | |
528 } | |
529 | |
530 results.push_back(WebTextCheckingResult( | |
531 static_cast<WebTextDecorationType>(decoration), | |
532 line_offset + spellcheck_result.location, spellcheck_result.length, | |
533 replacement, spellcheck_result.hash)); | |
534 } | |
535 | |
536 textcheck_results->assign(results); | |
537 } | |
538 | |
539 bool SpellCheck::IsSpellcheckEnabled() { | |
540 #if defined(OS_ANDROID) | |
541 if (!base::CommandLine::ForCurrentProcess()->HasSwitch( | |
542 spellcheck::switches::kEnableAndroidSpellChecker)) { | |
543 return false; | |
544 } | |
545 #endif | |
546 return spellcheck_enabled_; | |
547 } | |
OLD | NEW |