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