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/common/chrome_switches.h" | |
23 #include "chrome/common/spellcheck_common.h" | |
24 #include "chrome/common/spellcheck_messages.h" | |
25 #include "chrome/common/spellcheck_result.h" | |
26 #include "chrome/renderer/spellchecker/spellcheck_language.h" | |
27 #include "chrome/renderer/spellchecker/spellcheck_provider.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() >= | |
202 chrome::spellcheck_common::kMaxSuggestions) { | |
203 break; | |
204 } | |
205 } | |
206 } | |
207 | |
208 bool SpellCheck::OnControlMessageReceived(const IPC::Message& message) { | |
209 bool handled = true; | |
210 IPC_BEGIN_MESSAGE_MAP(SpellCheck, message) | |
211 IPC_MESSAGE_HANDLER(SpellCheckMsg_Init, OnInit) | |
212 IPC_MESSAGE_HANDLER(SpellCheckMsg_CustomDictionaryChanged, | |
213 OnCustomDictionaryChanged) | |
214 IPC_MESSAGE_HANDLER(SpellCheckMsg_EnableSpellCheck, OnEnableSpellCheck) | |
215 IPC_MESSAGE_HANDLER(SpellCheckMsg_RequestDocumentMarkers, | |
216 OnRequestDocumentMarkers) | |
217 IPC_MESSAGE_UNHANDLED(handled = false) | |
218 IPC_END_MESSAGE_MAP() | |
219 | |
220 return handled; | |
221 } | |
222 | |
223 void SpellCheck::OnInit( | |
224 const std::vector<SpellCheckBDictLanguage>& bdict_languages, | |
225 const std::set<std::string>& custom_words) { | |
226 languages_.clear(); | |
227 for (const auto& bdict_language : bdict_languages) { | |
228 AddSpellcheckLanguage( | |
229 IPC::PlatformFileForTransitToFile(bdict_language.file), | |
230 bdict_language.language); | |
231 } | |
232 | |
233 custom_dictionary_.Init(custom_words); | |
234 #if !defined(USE_BROWSER_SPELLCHECKER) | |
235 PostDelayedSpellCheckTask(pending_request_param_.release()); | |
236 #endif | |
237 } | |
238 | |
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()) | |
244 return; | |
245 DocumentMarkersRemover markersRemover(words_added); | |
246 content::RenderView::ForEach(&markersRemover); | |
247 } | |
248 | |
249 void SpellCheck::OnEnableSpellCheck(bool enable) { | |
250 spellcheck_enabled_ = enable; | |
251 UpdateSpellcheckEnabled updater(enable); | |
252 content::RenderView::ForEach(&updater); | |
253 } | |
254 | |
255 void SpellCheck::OnRequestDocumentMarkers() { | |
256 DocumentMarkersCollector collector; | |
257 content::RenderView::ForEach(&collector); | |
258 content::RenderThread::Get()->Send( | |
259 new SpellCheckHostMsg_RespondDocumentMarkers(collector.markers())); | |
260 } | |
261 | |
262 // TODO(groby): Make sure we always have a spelling engine, even before | |
263 // AddSpellcheckLanguage() is called. | |
264 void SpellCheck::AddSpellcheckLanguage(base::File file, | |
265 const std::string& language) { | |
266 languages_.push_back(new SpellcheckLanguage()); | |
267 languages_.back()->Init(std::move(file), language); | |
268 } | |
269 | |
270 bool SpellCheck::SpellCheckWord( | |
271 const base::char16* text_begin, | |
272 int position_in_text, | |
273 int text_length, | |
274 int tag, | |
275 int* misspelling_start, | |
276 int* misspelling_len, | |
277 std::vector<base::string16>* optional_suggestions) { | |
278 DCHECK(text_length >= position_in_text); | |
279 DCHECK(misspelling_start && misspelling_len) << "Out vars must be given."; | |
280 | |
281 // Do nothing if we need to delay initialization. (Rather than blocking, | |
282 // report the word as correctly spelled.) | |
283 if (InitializeIfNeeded()) | |
284 return true; | |
285 | |
286 // These are for holding misspelling or skippable word positions and lengths | |
287 // between calls to SpellcheckLanguage::SpellCheckWord. | |
288 int possible_misspelling_start; | |
289 int possible_misspelling_len; | |
290 // The longest sequence of text that all languages agree is skippable. | |
291 int agreed_skippable_len; | |
292 // A vector of vectors containing spelling suggestions from different | |
293 // languages. | |
294 std::vector<std::vector<base::string16>> suggestions_list; | |
295 // A vector to hold a language's misspelling suggestions between spellcheck | |
296 // calls. | |
297 std::vector<base::string16> language_suggestions; | |
298 | |
299 // This loop only advances if all languages agree that a sequence of text is | |
300 // skippable. | |
301 for (; position_in_text <= text_length; | |
302 position_in_text += agreed_skippable_len) { | |
303 // Reseting |agreed_skippable_len| to the worst-case length each time | |
304 // prevents some unnecessary iterations. | |
305 agreed_skippable_len = text_length; | |
306 *misspelling_start = 0; | |
307 *misspelling_len = 0; | |
308 suggestions_list.clear(); | |
309 | |
310 for (ScopedVector<SpellcheckLanguage>::iterator language = | |
311 languages_.begin(); | |
312 language != languages_.end();) { | |
313 language_suggestions.clear(); | |
314 SpellcheckLanguage::SpellcheckWordResult result = | |
315 (*language)->SpellCheckWord( | |
316 text_begin, position_in_text, text_length, tag, | |
317 &possible_misspelling_start, &possible_misspelling_len, | |
318 optional_suggestions ? &language_suggestions : nullptr); | |
319 | |
320 switch (result) { | |
321 case SpellcheckLanguage::SpellcheckWordResult::IS_CORRECT: | |
322 *misspelling_start = 0; | |
323 *misspelling_len = 0; | |
324 return true; | |
325 case SpellcheckLanguage::SpellcheckWordResult::IS_SKIPPABLE: | |
326 agreed_skippable_len = | |
327 std::min(agreed_skippable_len, possible_misspelling_len); | |
328 // If true, this means the spellchecker moved past a word that was | |
329 // previously determined to be misspelled or skippable, which means | |
330 // another spellcheck language marked it as correct. | |
331 if (position_in_text != possible_misspelling_start) { | |
332 *misspelling_len = 0; | |
333 position_in_text = possible_misspelling_start; | |
334 suggestions_list.clear(); | |
335 language = languages_.begin(); | |
336 } else { | |
337 language++; | |
338 } | |
339 break; | |
340 case SpellcheckLanguage::SpellcheckWordResult::IS_MISSPELLED: | |
341 *misspelling_start = possible_misspelling_start; | |
342 *misspelling_len = possible_misspelling_len; | |
343 // If true, this means the spellchecker moved past a word that was | |
344 // previously determined to be misspelled or skippable, which means | |
345 // another spellcheck language marked it as correct. | |
346 if (position_in_text != *misspelling_start) { | |
347 suggestions_list.clear(); | |
348 language = languages_.begin(); | |
349 position_in_text = *misspelling_start; | |
350 } else { | |
351 suggestions_list.push_back(language_suggestions); | |
352 language++; | |
353 } | |
354 break; | |
355 } | |
356 } | |
357 | |
358 // If |*misspelling_len| is non-zero, that means at least one language | |
359 // marked a word misspelled and no other language considered it correct. | |
360 if (*misspelling_len != 0) { | |
361 if (optional_suggestions) | |
362 FillSuggestions(suggestions_list, optional_suggestions); | |
363 return false; | |
364 } | |
365 } | |
366 | |
367 NOTREACHED(); | |
368 return true; | |
369 } | |
370 | |
371 bool SpellCheck::SpellCheckParagraph( | |
372 const base::string16& text, | |
373 WebVector<WebTextCheckingResult>* results) { | |
374 #if !defined(USE_BROWSER_SPELLCHECKER) | |
375 // Mac and Android have their own spell checkers,so this method won't be used | |
376 DCHECK(results); | |
377 std::vector<WebTextCheckingResult> textcheck_results; | |
378 size_t length = text.length(); | |
379 size_t position_in_text = 0; | |
380 | |
381 // Spellcheck::SpellCheckWord() automatically breaks text into words and | |
382 // checks the spellings of the extracted words. This function sets the | |
383 // position and length of the first misspelled word and returns false when | |
384 // the text includes misspelled words. Therefore, we just repeat calling the | |
385 // function until it returns true to check the whole text. | |
386 int misspelling_start = 0; | |
387 int misspelling_length = 0; | |
388 while (position_in_text <= length) { | |
389 if (SpellCheckWord(text.c_str(), | |
390 position_in_text, | |
391 length, | |
392 kNoTag, | |
393 &misspelling_start, | |
394 &misspelling_length, | |
395 NULL)) { | |
396 results->assign(textcheck_results); | |
397 return true; | |
398 } | |
399 | |
400 if (!custom_dictionary_.SpellCheckWord( | |
401 text, misspelling_start, misspelling_length)) { | |
402 base::string16 replacement; | |
403 textcheck_results.push_back(WebTextCheckingResult( | |
404 blink::WebTextDecorationTypeSpelling, | |
405 misspelling_start, | |
406 misspelling_length, | |
407 replacement)); | |
408 } | |
409 position_in_text = misspelling_start + misspelling_length; | |
410 } | |
411 results->assign(textcheck_results); | |
412 return false; | |
413 #else | |
414 // This function is only invoked for spell checker functionality that runs | |
415 // on the render thread. OSX and Android builds don't have that. | |
416 NOTREACHED(); | |
417 return true; | |
418 #endif | |
419 } | |
420 | |
421 // OSX and Android use their own spell checkers | |
422 #if !defined(USE_BROWSER_SPELLCHECKER) | |
423 void SpellCheck::RequestTextChecking( | |
424 const base::string16& text, | |
425 blink::WebTextCheckingCompletion* completion) { | |
426 // Clean up the previous request before starting a new request. | |
427 if (pending_request_param_.get()) | |
428 pending_request_param_->completion()->didCancelCheckingText(); | |
429 | |
430 pending_request_param_.reset(new SpellcheckRequest( | |
431 text, completion)); | |
432 // We will check this text after we finish loading the hunspell dictionary. | |
433 if (InitializeIfNeeded()) | |
434 return; | |
435 | |
436 PostDelayedSpellCheckTask(pending_request_param_.release()); | |
437 } | |
438 #endif | |
439 | |
440 bool SpellCheck::InitializeIfNeeded() { | |
441 if (languages_.empty()) | |
442 return true; | |
443 | |
444 bool initialize_if_needed = false; | |
445 for (SpellcheckLanguage* language : languages_) | |
446 initialize_if_needed |= language->InitializeIfNeeded(); | |
447 | |
448 return initialize_if_needed; | |
449 } | |
450 | |
451 // OSX and Android don't have |pending_request_param_| | |
452 #if !defined(USE_BROWSER_SPELLCHECKER) | |
453 void SpellCheck::PostDelayedSpellCheckTask(SpellcheckRequest* request) { | |
454 if (!request) | |
455 return; | |
456 | |
457 base::ThreadTaskRunnerHandle::Get()->PostTask( | |
458 FROM_HERE, base::Bind(&SpellCheck::PerformSpellCheck, AsWeakPtr(), | |
459 base::Owned(request))); | |
460 } | |
461 #endif | |
462 | |
463 // Mac and Android use their platform engines instead. | |
464 #if !defined(USE_BROWSER_SPELLCHECKER) | |
465 void SpellCheck::PerformSpellCheck(SpellcheckRequest* param) { | |
466 DCHECK(param); | |
467 | |
468 if (languages_.empty() || | |
469 std::find_if(languages_.begin(), languages_.end(), | |
470 [](SpellcheckLanguage* language) { | |
471 return !language->IsEnabled(); | |
472 }) != languages_.end()) { | |
473 param->completion()->didCancelCheckingText(); | |
474 } else { | |
475 WebVector<blink::WebTextCheckingResult> results; | |
476 SpellCheckParagraph(param->text(), &results); | |
477 param->completion()->didFinishCheckingText(results); | |
478 } | |
479 } | |
480 #endif | |
481 | |
482 void SpellCheck::CreateTextCheckingResults( | |
483 ResultFilter filter, | |
484 int line_offset, | |
485 const base::string16& line_text, | |
486 const std::vector<SpellCheckResult>& spellcheck_results, | |
487 WebVector<WebTextCheckingResult>* textcheck_results) { | |
488 DCHECK(!line_text.empty()); | |
489 | |
490 std::vector<WebTextCheckingResult> results; | |
491 for (const SpellCheckResult& spellcheck_result : spellcheck_results) { | |
492 DCHECK_LE(static_cast<size_t>(spellcheck_result.location), | |
493 line_text.length()); | |
494 DCHECK_LE(static_cast<size_t>(spellcheck_result.location + | |
495 spellcheck_result.length), | |
496 line_text.length()); | |
497 | |
498 const base::string16& misspelled_word = | |
499 line_text.substr(spellcheck_result.location, spellcheck_result.length); | |
500 base::string16 replacement = spellcheck_result.replacement; | |
501 SpellCheckResult::Decoration decoration = spellcheck_result.decoration; | |
502 | |
503 // Ignore words in custom dictionary. | |
504 if (custom_dictionary_.SpellCheckWord(misspelled_word, 0, | |
505 misspelled_word.length())) { | |
506 continue; | |
507 } | |
508 | |
509 // Use the same types of appostrophes as in the mispelled word. | |
510 PreserveOriginalApostropheTypes(misspelled_word, &replacement); | |
511 | |
512 // Ignore misspellings due the typographical apostrophe. | |
513 if (misspelled_word == replacement) | |
514 continue; | |
515 | |
516 if (filter == USE_NATIVE_CHECKER) { | |
517 // Double-check misspelled words with out spellchecker and attach grammar | |
518 // markers to them if our spellchecker tells us they are correct words, | |
519 // i.e. they are probably contextually-misspelled words. | |
520 int unused_misspelling_start = 0; | |
521 int unused_misspelling_length = 0; | |
522 if (decoration == SpellCheckResult::SPELLING && | |
523 SpellCheckWord(misspelled_word.c_str(), kNoOffset, | |
524 misspelled_word.length(), kNoTag, | |
525 &unused_misspelling_start, &unused_misspelling_length, | |
526 nullptr)) { | |
527 decoration = SpellCheckResult::GRAMMAR; | |
528 } | |
529 } | |
530 | |
531 results.push_back(WebTextCheckingResult( | |
532 static_cast<WebTextDecorationType>(decoration), | |
533 line_offset + spellcheck_result.location, spellcheck_result.length, | |
534 replacement, spellcheck_result.hash)); | |
535 } | |
536 | |
537 textcheck_results->assign(results); | |
538 } | |
539 | |
540 bool SpellCheck::IsSpellcheckEnabled() { | |
541 #if defined(OS_ANDROID) | |
542 if (!base::CommandLine::ForCurrentProcess()->HasSwitch( | |
543 switches::kEnableAndroidSpellChecker)) { | |
544 return false; | |
545 } | |
546 #endif | |
547 return spellcheck_enabled_; | |
548 } | |
OLD | NEW |