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