Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(211)

Side by Side Diff: chrome/renderer/spellchecker/spellcheck.cc

Issue 2198143002: Componentize spellcheck [3]: move renderer/ files to component. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: rebase Created 4 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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 }
OLDNEW
« no previous file with comments | « chrome/renderer/spellchecker/spellcheck.h ('k') | chrome/renderer/spellchecker/spellcheck_language.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698