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/renderer/spellchecker/spellcheck.h" | |
11 #include "chrome/renderer/spellchecker/spellcheck_language.h" | |
12 #include "components/spellcheck/common/spellcheck_marker.h" | |
13 #include "components/spellcheck/common/spellcheck_messages.h" | |
14 #include "components/spellcheck/common/spellcheck_result.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::requestCheckingOfText( | |
148 const WebString& text, | |
149 const WebVector<uint32_t>& markers, | |
150 const WebVector<unsigned>& marker_offsets, | |
151 WebTextCheckingCompletion* completion) { | |
152 std::vector<SpellCheckMarker> spellcheck_markers; | |
153 for (size_t i = 0; i < markers.size(); ++i) { | |
154 spellcheck_markers.push_back( | |
155 SpellCheckMarker(markers[i], marker_offsets[i])); | |
156 } | |
157 RequestTextChecking(text, completion, spellcheck_markers); | |
158 UMA_HISTOGRAM_COUNTS("SpellCheck.api.async", text.length()); | |
159 } | |
160 | |
161 void SpellCheckProvider::cancelAllPendingRequests() { | |
162 for (WebTextCheckCompletions::iterator iter(&text_check_completions_); | |
163 !iter.IsAtEnd(); iter.Advance()) { | |
164 iter.GetCurrentValue()->didCancelCheckingText(); | |
165 } | |
166 text_check_completions_.Clear(); | |
167 } | |
168 | |
169 void SpellCheckProvider::showSpellingUI(bool show) { | |
170 #if defined(USE_BROWSER_SPELLCHECKER) | |
171 UMA_HISTOGRAM_BOOLEAN("SpellCheck.api.showUI", show); | |
172 Send(new SpellCheckHostMsg_ShowSpellingPanel(routing_id(), show)); | |
173 #endif | |
174 } | |
175 | |
176 bool SpellCheckProvider::isShowingSpellingUI() { | |
177 return spelling_panel_visible_; | |
178 } | |
179 | |
180 void SpellCheckProvider::updateSpellingUIWithMisspelledWord( | |
181 const WebString& word) { | |
182 #if defined(USE_BROWSER_SPELLCHECKER) | |
183 Send(new SpellCheckHostMsg_UpdateSpellingPanelWithMisspelledWord(routing_id(), | |
184 word)); | |
185 #endif | |
186 } | |
187 | |
188 #if !defined(USE_BROWSER_SPELLCHECKER) | |
189 void SpellCheckProvider::OnRespondSpellingService( | |
190 int identifier, | |
191 bool succeeded, | |
192 const base::string16& line, | |
193 const std::vector<SpellCheckResult>& results) { | |
194 WebTextCheckingCompletion* completion = | |
195 text_check_completions_.Lookup(identifier); | |
196 if (!completion) | |
197 return; | |
198 text_check_completions_.Remove(identifier); | |
199 | |
200 // If |succeeded| is false, we use local spellcheck as a fallback. | |
201 if (!succeeded) { | |
202 spellcheck_->RequestTextChecking(line, completion); | |
203 return; | |
204 } | |
205 | |
206 // Double-check the returned spellchecking results with our spellchecker to | |
207 // visualize the differences between ours and the on-line spellchecker. | |
208 blink::WebVector<blink::WebTextCheckingResult> textcheck_results; | |
209 spellcheck_->CreateTextCheckingResults(SpellCheck::USE_NATIVE_CHECKER, | |
210 0, | |
211 line, | |
212 results, | |
213 &textcheck_results); | |
214 completion->didFinishCheckingText(textcheck_results); | |
215 | |
216 // Cache the request and the converted results. | |
217 last_request_ = line; | |
218 last_results_.swap(textcheck_results); | |
219 } | |
220 #endif | |
221 | |
222 bool SpellCheckProvider::HasWordCharacters( | |
223 const base::string16& text, | |
224 int index) const { | |
225 const base::char16* data = text.data(); | |
226 int length = text.length(); | |
227 while (index < length) { | |
228 uint32_t code = 0; | |
229 U16_NEXT(data, index, length, code); | |
230 UErrorCode error = U_ZERO_ERROR; | |
231 if (uscript_getScript(code, &error) != USCRIPT_COMMON) | |
232 return true; | |
233 } | |
234 return false; | |
235 } | |
236 | |
237 #if defined(USE_BROWSER_SPELLCHECKER) | |
238 void SpellCheckProvider::OnAdvanceToNextMisspelling() { | |
239 if (!render_view()->GetWebView()) | |
240 return; | |
241 render_view()->GetWebView()->focusedFrame()->executeCommand( | |
242 WebString::fromUTF8("AdvanceToNextMisspelling")); | |
243 } | |
244 | |
245 void SpellCheckProvider::OnRespondTextCheck( | |
246 int identifier, | |
247 const base::string16& line, | |
248 const std::vector<SpellCheckResult>& results) { | |
249 // TODO(groby): Unify with SpellCheckProvider::OnRespondSpellingService | |
250 DCHECK(spellcheck_); | |
251 WebTextCheckingCompletion* completion = | |
252 text_check_completions_.Lookup(identifier); | |
253 if (!completion) | |
254 return; | |
255 text_check_completions_.Remove(identifier); | |
256 blink::WebVector<blink::WebTextCheckingResult> textcheck_results; | |
257 spellcheck_->CreateTextCheckingResults(SpellCheck::DO_NOT_MODIFY, | |
258 0, | |
259 line, | |
260 results, | |
261 &textcheck_results); | |
262 completion->didFinishCheckingText(textcheck_results); | |
263 | |
264 // TODO(groby): Add request caching once OSX reports back original request. | |
265 // (cf. SpellCheckProvider::OnRespondSpellingService) | |
266 // Cache the request and the converted results. | |
267 } | |
268 | |
269 void SpellCheckProvider::OnToggleSpellPanel(bool is_currently_visible) { | |
270 if (!render_view()->GetWebView()) | |
271 return; | |
272 // We need to tell the webView whether the spelling panel is visible or not so | |
273 // that it won't need to make ipc calls later. | |
274 spelling_panel_visible_ = is_currently_visible; | |
275 render_view()->GetWebView()->focusedFrame()->executeCommand( | |
276 WebString::fromUTF8("ToggleSpellPanel")); | |
277 } | |
278 #endif | |
279 | |
280 void SpellCheckProvider::EnableSpellcheck(bool enable) { | |
281 if (!render_view()->GetWebView()) | |
282 return; | |
283 | |
284 WebLocalFrame* frame = render_view()->GetWebView()->focusedFrame(); | |
285 // TODO(yabinh): The null check should be unnecessary. | |
286 // See crbug.com/625068 | |
287 if (!frame) | |
288 return; | |
289 | |
290 frame->enableContinuousSpellChecking(enable); | |
291 if (!enable) | |
292 frame->removeSpellingMarkers(); | |
293 } | |
294 | |
295 bool SpellCheckProvider::SatisfyRequestFromCache( | |
296 const base::string16& text, | |
297 WebTextCheckingCompletion* completion) { | |
298 size_t last_length = last_request_.length(); | |
299 | |
300 // Send back the |last_results_| if the |last_request_| is a substring of | |
301 // |text| and |text| does not have more words to check. Provider cannot cancel | |
302 // the spellcheck request here, because WebKit might have discarded the | |
303 // previous spellcheck results and erased the spelling markers in response to | |
304 // the user editing the text. | |
305 base::string16 request(text); | |
306 size_t text_length = request.length(); | |
307 if (text_length >= last_length && | |
308 !request.compare(0, last_length, last_request_)) { | |
309 if (text_length == last_length || !HasWordCharacters(text, last_length)) { | |
310 completion->didFinishCheckingText(last_results_); | |
311 return true; | |
312 } | |
313 int code = 0; | |
314 int length = static_cast<int>(text_length); | |
315 U16_PREV(text.data(), 0, length, code); | |
316 UErrorCode error = U_ZERO_ERROR; | |
317 if (uscript_getScript(code, &error) != USCRIPT_COMMON) { | |
318 completion->didCancelCheckingText(); | |
319 return true; | |
320 } | |
321 } | |
322 // Create a subset of the cached results and return it if the given text is a | |
323 // substring of the cached text. | |
324 if (text_length < last_length && | |
325 !last_request_.compare(0, text_length, request)) { | |
326 size_t result_size = 0; | |
327 for (size_t i = 0; i < last_results_.size(); ++i) { | |
328 size_t start = last_results_[i].location; | |
329 size_t end = start + last_results_[i].length; | |
330 if (start <= text_length && end <= text_length) | |
331 ++result_size; | |
332 } | |
333 if (result_size > 0) { | |
334 blink::WebVector<blink::WebTextCheckingResult> results(result_size); | |
335 for (size_t i = 0; i < result_size; ++i) { | |
336 results[i].decoration = last_results_[i].decoration; | |
337 results[i].location = last_results_[i].location; | |
338 results[i].length = last_results_[i].length; | |
339 results[i].replacement = last_results_[i].replacement; | |
340 } | |
341 completion->didFinishCheckingText(results); | |
342 return true; | |
343 } | |
344 } | |
345 | |
346 return false; | |
347 } | |
348 | |
349 void SpellCheckProvider::OnDestruct() { | |
350 delete this; | |
351 } | |
OLD | NEW |