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

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

Issue 2159283003: [WIP][DO NOT LAND] Componentize spellcheck Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 4 years, 5 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/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 }
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