root/chrome/renderer/spellchecker/spellcheck_provider.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. spellcheck_
  2. RequestTextChecking
  3. OnMessageReceived
  4. FocusedNodeChanged
  5. spellCheck
  6. checkTextOfParagraph
  7. requestCheckingOfText
  8. autoCorrectWord
  9. showSpellingUI
  10. isShowingSpellingUI
  11. updateSpellingUIWithMisspelledWord
  12. OnRespondSpellingService
  13. HasWordCharacters
  14. OnAdvanceToNextMisspelling
  15. OnRespondTextCheck
  16. OnToggleSpellPanel
  17. EnableSpellcheck
  18. SatisfyRequestFromCache

// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/renderer/spellchecker/spellcheck_provider.h"

#include "base/command_line.h"
#include "base/metrics/histogram.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/spellcheck_marker.h"
#include "chrome/common/spellcheck_messages.h"
#include "chrome/common/spellcheck_result.h"
#include "chrome/renderer/spellchecker/spellcheck.h"
#include "content/public/renderer/render_view.h"
#include "third_party/WebKit/public/platform/WebVector.h"
#include "third_party/WebKit/public/web/WebElement.h"
#include "third_party/WebKit/public/web/WebFrame.h"
#include "third_party/WebKit/public/web/WebTextCheckingCompletion.h"
#include "third_party/WebKit/public/web/WebTextCheckingResult.h"
#include "third_party/WebKit/public/web/WebTextDecorationType.h"
#include "third_party/WebKit/public/web/WebView.h"

using blink::WebFrame;
using blink::WebString;
using blink::WebTextCheckingCompletion;
using blink::WebTextCheckingResult;
using blink::WebTextDecorationType;
using blink::WebVector;

COMPILE_ASSERT(int(blink::WebTextDecorationTypeSpelling) ==
               int(SpellCheckResult::SPELLING), mismatching_enums);
COMPILE_ASSERT(int(blink::WebTextDecorationTypeGrammar) ==
               int(SpellCheckResult::GRAMMAR), mismatching_enums);
COMPILE_ASSERT(int(blink::WebTextDecorationTypeInvisibleSpellcheck) ==
               int(SpellCheckResult::INVISIBLE), mismatching_enums);

SpellCheckProvider::SpellCheckProvider(
    content::RenderView* render_view,
    SpellCheck* spellcheck)
    : content::RenderViewObserver(render_view),
      content::RenderViewObserverTracker<SpellCheckProvider>(render_view),
      spelling_panel_visible_(false),
      spellcheck_(spellcheck) {
  DCHECK(spellcheck_);
  if (render_view) {  // NULL in unit tests.
    render_view->GetWebView()->setSpellCheckClient(this);
    EnableSpellcheck(spellcheck_->is_spellcheck_enabled());
  }
}

SpellCheckProvider::~SpellCheckProvider() {
}

void SpellCheckProvider::RequestTextChecking(
    const base::string16& text,
    WebTextCheckingCompletion* completion,
    const std::vector<SpellCheckMarker>& markers) {
  // Ignore invalid requests.
  if (text.empty() || !HasWordCharacters(text, 0)) {
    completion->didCancelCheckingText();
    return;
  }

  // Try to satisfy check from cache.
  if (SatisfyRequestFromCache(text, completion))
    return;

  // Send this text to a browser. A browser checks the user profile and send
  // this text to the Spelling service only if a user enables this feature.
  last_request_.clear();
  last_results_.assign(blink::WebVector<blink::WebTextCheckingResult>());

#if defined(OS_MACOSX)
  // Text check (unified request for grammar and spell check) is only
  // available for browser process, so we ask the system spellchecker
  // over IPC or return an empty result if the checker is not
  // available.
  Send(new SpellCheckHostMsg_RequestTextCheck(
      routing_id(),
      text_check_completions_.Add(completion),
      text,
      markers));
#else
  Send(new SpellCheckHostMsg_CallSpellingService(
      routing_id(),
      text_check_completions_.Add(completion),
      base::string16(text),
      markers));
#endif  // !OS_MACOSX
}

bool SpellCheckProvider::OnMessageReceived(const IPC::Message& message) {
  bool handled = true;
  IPC_BEGIN_MESSAGE_MAP(SpellCheckProvider, message)
#if !defined(OS_MACOSX)
    IPC_MESSAGE_HANDLER(SpellCheckMsg_RespondSpellingService,
                        OnRespondSpellingService)
#endif
#if defined(OS_MACOSX)
    IPC_MESSAGE_HANDLER(SpellCheckMsg_AdvanceToNextMisspelling,
                        OnAdvanceToNextMisspelling)
    IPC_MESSAGE_HANDLER(SpellCheckMsg_RespondTextCheck, OnRespondTextCheck)
    IPC_MESSAGE_HANDLER(SpellCheckMsg_ToggleSpellPanel, OnToggleSpellPanel)
#endif
    IPC_MESSAGE_UNHANDLED(handled = false)
  IPC_END_MESSAGE_MAP()
  return handled;
}

void SpellCheckProvider::FocusedNodeChanged(const blink::WebNode& unused) {
#if defined(OS_MACOSX)
  bool enabled = false;
  blink::WebElement element = render_view()->GetFocusedElement();
  if (!element.isNull())
    enabled = render_view()->IsEditableNode(element);

  bool checked = false;
  if (enabled && render_view()->GetWebView()) {
    WebFrame* frame = render_view()->GetWebView()->focusedFrame();
    if (frame->isContinuousSpellCheckingEnabled())
      checked = true;
  }

  Send(new SpellCheckHostMsg_ToggleSpellCheck(routing_id(), enabled, checked));
#endif  // OS_MACOSX
}

void SpellCheckProvider::spellCheck(
    const WebString& text,
    int& offset,
    int& length,
    WebVector<WebString>* optional_suggestions) {
  base::string16 word(text);
  std::vector<base::string16> suggestions;
  spellcheck_->SpellCheckWord(
      word.c_str(), word.size(), routing_id(),
      &offset, &length, optional_suggestions ? & suggestions : NULL);
  if (optional_suggestions) {
    *optional_suggestions = suggestions;
    UMA_HISTOGRAM_COUNTS("SpellCheck.api.check.suggestions", word.size());
  } else {
    UMA_HISTOGRAM_COUNTS("SpellCheck.api.check", word.size());
    // If optional_suggestions is not requested, the API is called
    // for marking.  So we use this for counting markable words.
    Send(new SpellCheckHostMsg_NotifyChecked(routing_id(), word, 0 < length));
  }
}

void SpellCheckProvider::checkTextOfParagraph(
    const blink::WebString& text,
    blink::WebTextCheckingTypeMask mask,
    blink::WebVector<blink::WebTextCheckingResult>* results) {
  if (!results)
    return;

  if (!(mask & blink::WebTextCheckingTypeSpelling))
    return;

  // TODO(groby): As far as I can tell, this method is never invoked.
  // UMA results seem to support that. Investigate, clean up if true.
  NOTREACHED();
  spellcheck_->SpellCheckParagraph(text, results);
  UMA_HISTOGRAM_COUNTS("SpellCheck.api.paragraph", text.length());
}

void SpellCheckProvider::requestCheckingOfText(
    const WebString& text,
    const WebVector<uint32>& markers,
    const WebVector<unsigned>& marker_offsets,
    WebTextCheckingCompletion* completion) {
  std::vector<SpellCheckMarker> spellcheck_markers;
  for (size_t i = 0; i < markers.size(); ++i) {
    spellcheck_markers.push_back(
        SpellCheckMarker(markers[i], marker_offsets[i]));
  }
  RequestTextChecking(text, completion, spellcheck_markers);
  UMA_HISTOGRAM_COUNTS("SpellCheck.api.async", text.length());
}

WebString SpellCheckProvider::autoCorrectWord(const WebString& word) {
  const CommandLine& command_line = *CommandLine::ForCurrentProcess();
  if (command_line.HasSwitch(switches::kEnableSpellingAutoCorrect)) {
    UMA_HISTOGRAM_COUNTS("SpellCheck.api.autocorrect", word.length());
    return spellcheck_->GetAutoCorrectionWord(word, routing_id());
  }
  return base::string16();
}

void SpellCheckProvider::showSpellingUI(bool show) {
#if defined(OS_MACOSX)
  UMA_HISTOGRAM_BOOLEAN("SpellCheck.api.showUI", show);
  Send(new SpellCheckHostMsg_ShowSpellingPanel(routing_id(), show));
#endif
}

bool SpellCheckProvider::isShowingSpellingUI() {
  return spelling_panel_visible_;
}

void SpellCheckProvider::updateSpellingUIWithMisspelledWord(
    const WebString& word) {
#if defined(OS_MACOSX)
  Send(new SpellCheckHostMsg_UpdateSpellingPanelWithMisspelledWord(routing_id(),
                                                                   word));
#endif
}

#if !defined(OS_MACOSX)
void SpellCheckProvider::OnRespondSpellingService(
    int identifier,
    bool succeeded,
    const base::string16& line,
    const std::vector<SpellCheckResult>& results) {
  WebTextCheckingCompletion* completion =
      text_check_completions_.Lookup(identifier);
  if (!completion)
    return;
  text_check_completions_.Remove(identifier);

  // If |succeeded| is false, we use local spellcheck as a fallback.
  if (!succeeded) {
    spellcheck_->RequestTextChecking(line, completion);
    return;
  }

  // Double-check the returned spellchecking results with our spellchecker to
  // visualize the differences between ours and the on-line spellchecker.
  blink::WebVector<blink::WebTextCheckingResult> textcheck_results;
  spellcheck_->CreateTextCheckingResults(SpellCheck::USE_NATIVE_CHECKER,
                                         0,
                                         line,
                                         results,
                                         &textcheck_results);
  completion->didFinishCheckingText(textcheck_results);

  // Cache the request and the converted results.
  last_request_ = line;
  last_results_.swap(textcheck_results);
}
#endif

bool SpellCheckProvider::HasWordCharacters(
    const base::string16& text,
    int index) const {
  const base::char16* data = text.data();
  int length = text.length();
  while (index < length) {
    uint32 code = 0;
    U16_NEXT(data, index, length, code);
    UErrorCode error = U_ZERO_ERROR;
    if (uscript_getScript(code, &error) != USCRIPT_COMMON)
      return true;
  }
  return false;
}

#if defined(OS_MACOSX)
void SpellCheckProvider::OnAdvanceToNextMisspelling() {
  if (!render_view()->GetWebView())
    return;
  render_view()->GetWebView()->focusedFrame()->executeCommand(
      WebString::fromUTF8("AdvanceToNextMisspelling"));
}

void SpellCheckProvider::OnRespondTextCheck(
    int identifier,
    const std::vector<SpellCheckResult>& results) {
  // TODO(groby): Unify with SpellCheckProvider::OnRespondSpellingService
  DCHECK(spellcheck_);
  WebTextCheckingCompletion* completion =
      text_check_completions_.Lookup(identifier);
  if (!completion)
    return;
  text_check_completions_.Remove(identifier);
  blink::WebVector<blink::WebTextCheckingResult> textcheck_results;
  spellcheck_->CreateTextCheckingResults(SpellCheck::DO_NOT_MODIFY,
                                         0,
                                         base::string16(),
                                         results,
                                         &textcheck_results);
  completion->didFinishCheckingText(textcheck_results);

  // TODO(groby): Add request caching once OSX reports back original request.
  // (cf. SpellCheckProvider::OnRespondSpellingService)
  // Cache the request and the converted results.
}

void SpellCheckProvider::OnToggleSpellPanel(bool is_currently_visible) {
  if (!render_view()->GetWebView())
    return;
  // We need to tell the webView whether the spelling panel is visible or not so
  // that it won't need to make ipc calls later.
  spelling_panel_visible_ = is_currently_visible;
  render_view()->GetWebView()->focusedFrame()->executeCommand(
      WebString::fromUTF8("ToggleSpellPanel"));
}
#endif

void SpellCheckProvider::EnableSpellcheck(bool enable) {
  if (!render_view()->GetWebView())
    return;

  WebFrame* frame = render_view()->GetWebView()->focusedFrame();
  frame->enableContinuousSpellChecking(enable);
  if (!enable)
    frame->removeSpellingMarkers();
}

bool SpellCheckProvider::SatisfyRequestFromCache(
    const base::string16& text,
    WebTextCheckingCompletion* completion) {
  size_t last_length = last_request_.length();

  // Send back the |last_results_| if the |last_request_| is a substring of
  // |text| and |text| does not have more words to check. Provider cannot cancel
  // the spellcheck request here, because WebKit might have discarded the
  // previous spellcheck results and erased the spelling markers in response to
  // the user editing the text.
  base::string16 request(text);
  size_t text_length = request.length();
  if (text_length >= last_length &&
      !request.compare(0, last_length, last_request_)) {
    if (text_length == last_length || !HasWordCharacters(text, last_length)) {
      completion->didFinishCheckingText(last_results_);
      return true;
    }
    int code = 0;
    int length = static_cast<int>(text_length);
    U16_PREV(text.data(), 0, length, code);
    UErrorCode error = U_ZERO_ERROR;
    if (uscript_getScript(code, &error) != USCRIPT_COMMON) {
      completion->didCancelCheckingText();
      return true;
    }
  }
  // Create a subset of the cached results and return it if the given text is a
  // substring of the cached text.
  if (text_length < last_length &&
      !last_request_.compare(0, text_length, request)) {
    size_t result_size = 0;
    for (size_t i = 0; i < last_results_.size(); ++i) {
      size_t start = last_results_[i].location;
      size_t end = start + last_results_[i].length;
      if (start <= text_length && end <= text_length)
        ++result_size;
    }
    if (result_size > 0) {
      blink::WebVector<blink::WebTextCheckingResult> results(result_size);
      for (size_t i = 0; i < result_size; ++i) {
        results[i].decoration = last_results_[i].decoration;
        results[i].location = last_results_[i].location;
        results[i].length = last_results_[i].length;
        results[i].replacement = last_results_[i].replacement;
      }
      completion->didFinishCheckingText(results);
      return true;
    }
  }

  return false;
}

/* [<][>][^][v][top][bottom][index][help] */