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