Home | History | Annotate | Download | only in spellchecker
      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 
     23 using blink::WebFrame;
     24 using blink::WebString;
     25 using blink::WebTextCheckingCompletion;
     26 using blink::WebTextCheckingResult;
     27 using blink::WebTextDecorationType;
     28 using blink::WebVector;
     29 
     30 COMPILE_ASSERT(int(blink::WebTextDecorationTypeSpelling) ==
     31                int(SpellCheckResult::SPELLING), mismatching_enums);
     32 COMPILE_ASSERT(int(blink::WebTextDecorationTypeGrammar) ==
     33                int(SpellCheckResult::GRAMMAR), mismatching_enums);
     34 COMPILE_ASSERT(int(blink::WebTextDecorationTypeInvisibleSpellcheck) ==
     35                int(SpellCheckResult::INVISIBLE), mismatching_enums);
     36 
     37 SpellCheckProvider::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 
     51 SpellCheckProvider::~SpellCheckProvider() {
     52 }
     53 
     54 void 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 
     92 bool 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 
    110 void 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 
    128 void 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 
    149 void 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 
    166 void 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 
    180 WebString 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 
    189 void 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 
    196 bool SpellCheckProvider::isShowingSpellingUI() {
    197   return spelling_panel_visible_;
    198 }
    199 
    200 void 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)
    209 void 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 
    242 bool 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)
    258 void SpellCheckProvider::OnAdvanceToNextMisspelling() {
    259   if (!render_view()->GetWebView())
    260     return;
    261   render_view()->GetWebView()->focusedFrame()->executeCommand(
    262       WebString::fromUTF8("AdvanceToNextMisspelling"));
    263 }
    264 
    265 void 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 
    288 void 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 
    299 void 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 
    309 bool 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