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