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