• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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_provider.h"
6 
7 #include "base/command_line.h"
8 #include "base/metrics/histogram.h"
9 #include "chrome/common/chrome_switches.h"
10 #include "chrome/common/spellcheck_marker.h"
11 #include "chrome/common/spellcheck_messages.h"
12 #include "chrome/common/spellcheck_result.h"
13 #include "chrome/renderer/spellchecker/spellcheck.h"
14 #include "content/public/renderer/render_view.h"
15 #include "third_party/WebKit/public/platform/WebVector.h"
16 #include "third_party/WebKit/public/web/WebFrame.h"
17 #include "third_party/WebKit/public/web/WebTextCheckingCompletion.h"
18 #include "third_party/WebKit/public/web/WebTextCheckingResult.h"
19 #include "third_party/WebKit/public/web/WebTextDecorationType.h"
20 #include "third_party/WebKit/public/web/WebView.h"
21 
22 using blink::WebFrame;
23 using blink::WebString;
24 using blink::WebTextCheckingCompletion;
25 using blink::WebTextCheckingResult;
26 using blink::WebTextDecorationType;
27 using blink::WebVector;
28 
29 COMPILE_ASSERT(int(blink::WebTextDecorationTypeSpelling) ==
30                int(SpellCheckResult::SPELLING), mismatching_enums);
31 COMPILE_ASSERT(int(blink::WebTextDecorationTypeGrammar) ==
32                int(SpellCheckResult::GRAMMAR), mismatching_enums);
33 COMPILE_ASSERT(int(blink::WebTextDecorationTypeInvisibleSpellcheck) ==
34                int(SpellCheckResult::INVISIBLE), mismatching_enums);
35 
SpellCheckProvider(content::RenderView * render_view,SpellCheck * spellcheck)36 SpellCheckProvider::SpellCheckProvider(
37     content::RenderView* render_view,
38     SpellCheck* spellcheck)
39     : content::RenderViewObserver(render_view),
40       content::RenderViewObserverTracker<SpellCheckProvider>(render_view),
41       spelling_panel_visible_(false),
42       spellcheck_(spellcheck) {
43   DCHECK(spellcheck_);
44   if (render_view) {  // NULL in unit tests.
45     render_view->GetWebView()->setSpellCheckClient(this);
46     EnableSpellcheck(spellcheck_->is_spellcheck_enabled());
47   }
48 }
49 
~SpellCheckProvider()50 SpellCheckProvider::~SpellCheckProvider() {
51 }
52 
RequestTextChecking(const base::string16 & text,WebTextCheckingCompletion * completion,const std::vector<SpellCheckMarker> & markers)53 void SpellCheckProvider::RequestTextChecking(
54     const base::string16& text,
55     WebTextCheckingCompletion* completion,
56     const std::vector<SpellCheckMarker>& markers) {
57   // Ignore invalid requests.
58   if (text.empty() || !HasWordCharacters(text, 0)) {
59     completion->didCancelCheckingText();
60     return;
61   }
62 
63   // Try to satisfy check from cache.
64   if (SatisfyRequestFromCache(text, completion))
65     return;
66 
67   // Send this text to a browser. A browser checks the user profile and send
68   // this text to the Spelling service only if a user enables this feature.
69   last_request_.clear();
70   last_results_.assign(blink::WebVector<blink::WebTextCheckingResult>());
71 
72 #if defined(OS_MACOSX)
73   // Text check (unified request for grammar and spell check) is only
74   // available for browser process, so we ask the system spellchecker
75   // over IPC or return an empty result if the checker is not
76   // available.
77   Send(new SpellCheckHostMsg_RequestTextCheck(
78       routing_id(),
79       text_check_completions_.Add(completion),
80       text,
81       markers));
82 #else
83   Send(new SpellCheckHostMsg_CallSpellingService(
84       routing_id(),
85       text_check_completions_.Add(completion),
86       base::string16(text),
87       markers));
88 #endif  // !OS_MACOSX
89 }
90 
OnMessageReceived(const IPC::Message & message)91 bool SpellCheckProvider::OnMessageReceived(const IPC::Message& message) {
92   bool handled = true;
93   IPC_BEGIN_MESSAGE_MAP(SpellCheckProvider, message)
94 #if !defined(OS_MACOSX)
95     IPC_MESSAGE_HANDLER(SpellCheckMsg_RespondSpellingService,
96                         OnRespondSpellingService)
97 #endif
98 #if defined(OS_MACOSX)
99     IPC_MESSAGE_HANDLER(SpellCheckMsg_AdvanceToNextMisspelling,
100                         OnAdvanceToNextMisspelling)
101     IPC_MESSAGE_HANDLER(SpellCheckMsg_RespondTextCheck, OnRespondTextCheck)
102     IPC_MESSAGE_HANDLER(SpellCheckMsg_ToggleSpellPanel, OnToggleSpellPanel)
103 #endif
104     IPC_MESSAGE_UNHANDLED(handled = false)
105   IPC_END_MESSAGE_MAP()
106   return handled;
107 }
108 
FocusedNodeChanged(const blink::WebNode & unused)109 void SpellCheckProvider::FocusedNodeChanged(const blink::WebNode& unused) {
110 #if defined(OS_MACOSX)
111   bool enabled = false;
112   blink::WebNode node = render_view()->GetFocusedNode();
113   if (!node.isNull())
114     enabled = render_view()->IsEditableNode(node);
115 
116   bool checked = false;
117   if (enabled && render_view()->GetWebView()) {
118     WebFrame* frame = render_view()->GetWebView()->focusedFrame();
119     if (frame->isContinuousSpellCheckingEnabled())
120       checked = true;
121   }
122 
123   Send(new SpellCheckHostMsg_ToggleSpellCheck(routing_id(), enabled, checked));
124 #endif  // OS_MACOSX
125 }
126 
spellCheck(const WebString & text,int & offset,int & length,WebVector<WebString> * optional_suggestions)127 void SpellCheckProvider::spellCheck(
128     const WebString& text,
129     int& offset,
130     int& length,
131     WebVector<WebString>* optional_suggestions) {
132   base::string16 word(text);
133   std::vector<base::string16> suggestions;
134   spellcheck_->SpellCheckWord(
135       word.c_str(), word.size(), routing_id(),
136       &offset, &length, optional_suggestions ? & suggestions : NULL);
137   if (optional_suggestions) {
138     *optional_suggestions = suggestions;
139     UMA_HISTOGRAM_COUNTS("SpellCheck.api.check.suggestions", word.size());
140   } else {
141     UMA_HISTOGRAM_COUNTS("SpellCheck.api.check", word.size());
142     // If optional_suggestions is not requested, the API is called
143     // for marking.  So we use this for counting markable words.
144     Send(new SpellCheckHostMsg_NotifyChecked(routing_id(), word, 0 < length));
145   }
146 }
147 
checkTextOfParagraph(const blink::WebString & text,blink::WebTextCheckingTypeMask mask,blink::WebVector<blink::WebTextCheckingResult> * results)148 void SpellCheckProvider::checkTextOfParagraph(
149     const blink::WebString& text,
150     blink::WebTextCheckingTypeMask mask,
151     blink::WebVector<blink::WebTextCheckingResult>* results) {
152   if (!results)
153     return;
154 
155   if (!(mask & blink::WebTextCheckingTypeSpelling))
156     return;
157 
158   // TODO(groby): As far as I can tell, this method is never invoked.
159   // UMA results seem to support that. Investigate, clean up if true.
160   NOTREACHED();
161   spellcheck_->SpellCheckParagraph(text, results);
162   UMA_HISTOGRAM_COUNTS("SpellCheck.api.paragraph", text.length());
163 }
164 
requestCheckingOfText(const WebString & text,const WebVector<uint32> & markers,const WebVector<unsigned> & marker_offsets,WebTextCheckingCompletion * completion)165 void SpellCheckProvider::requestCheckingOfText(
166     const WebString& text,
167     const WebVector<uint32>& markers,
168     const WebVector<unsigned>& marker_offsets,
169     WebTextCheckingCompletion* completion) {
170   std::vector<SpellCheckMarker> spellcheck_markers;
171   for (size_t i = 0; i < markers.size(); ++i) {
172     spellcheck_markers.push_back(
173         SpellCheckMarker(markers[i], marker_offsets[i]));
174   }
175   RequestTextChecking(text, completion, spellcheck_markers);
176   UMA_HISTOGRAM_COUNTS("SpellCheck.api.async", text.length());
177 }
178 
autoCorrectWord(const WebString & word)179 WebString SpellCheckProvider::autoCorrectWord(const WebString& word) {
180   const CommandLine& command_line = *CommandLine::ForCurrentProcess();
181   if (command_line.HasSwitch(switches::kEnableSpellingAutoCorrect)) {
182     UMA_HISTOGRAM_COUNTS("SpellCheck.api.autocorrect", word.length());
183     return spellcheck_->GetAutoCorrectionWord(word, routing_id());
184   }
185   return base::string16();
186 }
187 
showSpellingUI(bool show)188 void SpellCheckProvider::showSpellingUI(bool show) {
189 #if defined(OS_MACOSX)
190   UMA_HISTOGRAM_BOOLEAN("SpellCheck.api.showUI", show);
191   Send(new SpellCheckHostMsg_ShowSpellingPanel(routing_id(), show));
192 #endif
193 }
194 
isShowingSpellingUI()195 bool SpellCheckProvider::isShowingSpellingUI() {
196   return spelling_panel_visible_;
197 }
198 
updateSpellingUIWithMisspelledWord(const WebString & word)199 void SpellCheckProvider::updateSpellingUIWithMisspelledWord(
200     const WebString& word) {
201 #if defined(OS_MACOSX)
202   Send(new SpellCheckHostMsg_UpdateSpellingPanelWithMisspelledWord(routing_id(),
203                                                                    word));
204 #endif
205 }
206 
207 #if !defined(OS_MACOSX)
OnRespondSpellingService(int identifier,bool succeeded,const base::string16 & line,const std::vector<SpellCheckResult> & results)208 void SpellCheckProvider::OnRespondSpellingService(
209     int identifier,
210     bool succeeded,
211     const base::string16& line,
212     const std::vector<SpellCheckResult>& results) {
213   WebTextCheckingCompletion* completion =
214       text_check_completions_.Lookup(identifier);
215   if (!completion)
216     return;
217   text_check_completions_.Remove(identifier);
218 
219   // If |succeeded| is false, we use local spellcheck as a fallback.
220   if (!succeeded) {
221     spellcheck_->RequestTextChecking(line, completion);
222     return;
223   }
224 
225   // Double-check the returned spellchecking results with our spellchecker to
226   // visualize the differences between ours and the on-line spellchecker.
227   blink::WebVector<blink::WebTextCheckingResult> textcheck_results;
228   spellcheck_->CreateTextCheckingResults(SpellCheck::USE_NATIVE_CHECKER,
229                                          0,
230                                          line,
231                                          results,
232                                          &textcheck_results);
233   completion->didFinishCheckingText(textcheck_results);
234 
235   // Cache the request and the converted results.
236   last_request_ = line;
237   last_results_.swap(textcheck_results);
238 }
239 #endif
240 
HasWordCharacters(const base::string16 & text,int index) const241 bool SpellCheckProvider::HasWordCharacters(
242     const base::string16& text,
243     int index) const {
244   const char16* data = text.data();
245   int length = text.length();
246   while (index < length) {
247     uint32 code = 0;
248     U16_NEXT(data, index, length, code);
249     UErrorCode error = U_ZERO_ERROR;
250     if (uscript_getScript(code, &error) != USCRIPT_COMMON)
251       return true;
252   }
253   return false;
254 }
255 
256 #if defined(OS_MACOSX)
OnAdvanceToNextMisspelling()257 void SpellCheckProvider::OnAdvanceToNextMisspelling() {
258   if (!render_view()->GetWebView())
259     return;
260   render_view()->GetWebView()->focusedFrame()->executeCommand(
261       WebString::fromUTF8("AdvanceToNextMisspelling"));
262 }
263 
OnRespondTextCheck(int identifier,const std::vector<SpellCheckResult> & results)264 void SpellCheckProvider::OnRespondTextCheck(
265     int identifier,
266     const std::vector<SpellCheckResult>& results) {
267   // TODO(groby): Unify with SpellCheckProvider::OnRespondSpellingService
268   DCHECK(spellcheck_);
269   WebTextCheckingCompletion* completion =
270       text_check_completions_.Lookup(identifier);
271   if (!completion)
272     return;
273   text_check_completions_.Remove(identifier);
274   blink::WebVector<blink::WebTextCheckingResult> textcheck_results;
275   spellcheck_->CreateTextCheckingResults(SpellCheck::DO_NOT_MODIFY,
276                                          0,
277                                          base::string16(),
278                                          results,
279                                          &textcheck_results);
280   completion->didFinishCheckingText(textcheck_results);
281 
282   // TODO(groby): Add request caching once OSX reports back original request.
283   // (cf. SpellCheckProvider::OnRespondSpellingService)
284   // Cache the request and the converted results.
285 }
286 
OnToggleSpellPanel(bool is_currently_visible)287 void SpellCheckProvider::OnToggleSpellPanel(bool is_currently_visible) {
288   if (!render_view()->GetWebView())
289     return;
290   // We need to tell the webView whether the spelling panel is visible or not so
291   // that it won't need to make ipc calls later.
292   spelling_panel_visible_ = is_currently_visible;
293   render_view()->GetWebView()->focusedFrame()->executeCommand(
294       WebString::fromUTF8("ToggleSpellPanel"));
295 }
296 #endif
297 
EnableSpellcheck(bool enable)298 void SpellCheckProvider::EnableSpellcheck(bool enable) {
299   if (!render_view()->GetWebView())
300     return;
301 
302   WebFrame* frame = render_view()->GetWebView()->focusedFrame();
303   frame->enableContinuousSpellChecking(enable);
304   if (!enable)
305     frame->removeSpellingMarkers();
306 }
307 
SatisfyRequestFromCache(const base::string16 & text,WebTextCheckingCompletion * completion)308 bool SpellCheckProvider::SatisfyRequestFromCache(
309     const base::string16& text,
310     WebTextCheckingCompletion* completion) {
311   size_t last_length = last_request_.length();
312 
313   // Send back the |last_results_| if the |last_request_| is a substring of
314   // |text| and |text| does not have more words to check. Provider cannot cancel
315   // the spellcheck request here, because WebKit might have discarded the
316   // previous spellcheck results and erased the spelling markers in response to
317   // the user editing the text.
318   base::string16 request(text);
319   size_t text_length = request.length();
320   if (text_length >= last_length &&
321       !request.compare(0, last_length, last_request_)) {
322     if (text_length == last_length || !HasWordCharacters(text, last_length)) {
323       completion->didFinishCheckingText(last_results_);
324       return true;
325     }
326     int code = 0;
327     int length = static_cast<int>(text_length);
328     U16_PREV(text.data(), 0, length, code);
329     UErrorCode error = U_ZERO_ERROR;
330     if (uscript_getScript(code, &error) != USCRIPT_COMMON) {
331       completion->didCancelCheckingText();
332       return true;
333     }
334   }
335   // Create a subset of the cached results and return it if the given text is a
336   // substring of the cached text.
337   if (text_length < last_length &&
338       !last_request_.compare(0, text_length, request)) {
339     size_t result_size = 0;
340     for (size_t i = 0; i < last_results_.size(); ++i) {
341       size_t start = last_results_[i].location;
342       size_t end = start + last_results_[i].length;
343       if (start <= text_length && end <= text_length)
344         ++result_size;
345     }
346     if (result_size > 0) {
347       blink::WebVector<blink::WebTextCheckingResult> results(result_size);
348       for (size_t i = 0; i < result_size; ++i) {
349         results[i].decoration = last_results_[i].decoration;
350         results[i].location = last_results_[i].location;
351         results[i].length = last_results_[i].length;
352         results[i].replacement = last_results_[i].replacement;
353       }
354       completion->didFinishCheckingText(results);
355       return true;
356     }
357   }
358 
359   return false;
360 }
361