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 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 50 SpellCheckProvider::~SpellCheckProvider() { 51 } 52 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 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 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 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 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 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 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 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 195 bool SpellCheckProvider::isShowingSpellingUI() { 196 return spelling_panel_visible_; 197 } 198 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) 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 241 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) 257 void SpellCheckProvider::OnAdvanceToNextMisspelling() { 258 if (!render_view()->GetWebView()) 259 return; 260 render_view()->GetWebView()->focusedFrame()->executeCommand( 261 WebString::fromUTF8("AdvanceToNextMisspelling")); 262 } 263 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 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 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 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