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