| 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::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 |