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