blob: 2150dd2f91130e0522f74a3154e71b05ee9cca16 [file] [log] [blame]
// 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.h"
#include "base/bind.h"
#include "base/message_loop/message_loop_proxy.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/common/render_messages.h"
#include "chrome/common/spellcheck_common.h"
#include "chrome/common/spellcheck_messages.h"
#include "chrome/common/spellcheck_result.h"
#include "chrome/renderer/spellchecker/spellcheck_language.h"
#include "chrome/renderer/spellchecker/spellcheck_provider.h"
#include "content/public/renderer/render_thread.h"
#include "content/public/renderer/render_view.h"
#include "content/public/renderer/render_view_visitor.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::WebVector;
using blink::WebTextCheckingResult;
using blink::WebTextDecorationType;
namespace {
class UpdateSpellcheckEnabled : public content::RenderViewVisitor {
public:
explicit UpdateSpellcheckEnabled(bool enabled) : enabled_(enabled) {}
virtual bool Visit(content::RenderView* render_view) OVERRIDE;
private:
bool enabled_; // New spellcheck-enabled state.
DISALLOW_COPY_AND_ASSIGN(UpdateSpellcheckEnabled);
};
bool UpdateSpellcheckEnabled::Visit(content::RenderView* render_view) {
SpellCheckProvider* provider = SpellCheckProvider::Get(render_view);
DCHECK(provider);
provider->EnableSpellcheck(enabled_);
return true;
}
class DocumentMarkersCollector : public content::RenderViewVisitor {
public:
DocumentMarkersCollector() {}
virtual ~DocumentMarkersCollector() {}
const std::vector<uint32>& markers() const { return markers_; }
virtual bool Visit(content::RenderView* render_view) OVERRIDE;
private:
std::vector<uint32> markers_;
DISALLOW_COPY_AND_ASSIGN(DocumentMarkersCollector);
};
bool DocumentMarkersCollector::Visit(content::RenderView* render_view) {
if (!render_view || !render_view->GetWebView())
return true;
WebVector<uint32> markers;
render_view->GetWebView()->spellingMarkers(&markers);
for (size_t i = 0; i < markers.size(); ++i)
markers_.push_back(markers[i]);
// Visit all render views.
return true;
}
} // namespace
class SpellCheck::SpellcheckRequest {
public:
SpellcheckRequest(const base::string16& text,
blink::WebTextCheckingCompletion* completion)
: text_(text), completion_(completion) {
DCHECK(completion);
}
~SpellcheckRequest() {}
base::string16 text() { return text_; }
blink::WebTextCheckingCompletion* completion() { return completion_; }
private:
base::string16 text_; // Text to be checked in this task.
// The interface to send the misspelled ranges to WebKit.
blink::WebTextCheckingCompletion* completion_;
DISALLOW_COPY_AND_ASSIGN(SpellcheckRequest);
};
// Initializes SpellCheck object.
// spellcheck_enabled_ currently MUST be set to true, due to peculiarities of
// the initialization sequence.
// Since it defaults to true, newly created SpellCheckProviders will enable
// spellchecking. After the first word is typed, the provider requests a check,
// which in turn triggers the delayed initialization sequence in SpellCheck.
// This does send a message to the browser side, which triggers the creation
// of the SpellcheckService. That does create the observer for the preference
// responsible for enabling/disabling checking, which allows subsequent changes
// to that preference to be sent to all SpellCheckProviders.
// Setting |spellcheck_enabled_| to false by default prevents that mechanism,
// and as such the SpellCheckProviders will never be notified of different
// values.
// TODO(groby): Simplify this.
SpellCheck::SpellCheck()
: auto_spell_correct_turned_on_(false),
spellcheck_enabled_(true) {
}
SpellCheck::~SpellCheck() {
}
bool SpellCheck::OnControlMessageReceived(const IPC::Message& message) {
bool handled = true;
IPC_BEGIN_MESSAGE_MAP(SpellCheck, message)
IPC_MESSAGE_HANDLER(SpellCheckMsg_Init, OnInit)
IPC_MESSAGE_HANDLER(SpellCheckMsg_CustomDictionaryChanged,
OnCustomDictionaryChanged)
IPC_MESSAGE_HANDLER(SpellCheckMsg_EnableAutoSpellCorrect,
OnEnableAutoSpellCorrect)
IPC_MESSAGE_HANDLER(SpellCheckMsg_EnableSpellCheck, OnEnableSpellCheck)
IPC_MESSAGE_HANDLER(SpellCheckMsg_RequestDocumentMarkers,
OnRequestDocumentMarkers)
IPC_MESSAGE_UNHANDLED(handled = false)
IPC_END_MESSAGE_MAP()
return handled;
}
void SpellCheck::OnInit(IPC::PlatformFileForTransit bdict_file,
const std::set<std::string>& custom_words,
const std::string& language,
bool auto_spell_correct) {
Init(IPC::PlatformFileForTransitToFile(bdict_file),
custom_words, language);
auto_spell_correct_turned_on_ = auto_spell_correct;
#if !defined(OS_MACOSX)
PostDelayedSpellCheckTask(pending_request_param_.release());
#endif
}
void SpellCheck::OnCustomDictionaryChanged(
const std::vector<std::string>& words_added,
const std::vector<std::string>& words_removed) {
custom_dictionary_.OnCustomDictionaryChanged(words_added, words_removed);
}
void SpellCheck::OnEnableAutoSpellCorrect(bool enable) {
auto_spell_correct_turned_on_ = enable;
}
void SpellCheck::OnEnableSpellCheck(bool enable) {
spellcheck_enabled_ = enable;
UpdateSpellcheckEnabled updater(enable);
content::RenderView::ForEach(&updater);
}
void SpellCheck::OnRequestDocumentMarkers() {
DocumentMarkersCollector collector;
content::RenderView::ForEach(&collector);
content::RenderThread::Get()->Send(
new SpellCheckHostMsg_RespondDocumentMarkers(collector.markers()));
}
// TODO(groby): Make sure we always have a spelling engine, even before Init()
// is called.
void SpellCheck::Init(base::File file,
const std::set<std::string>& custom_words,
const std::string& language) {
spellcheck_.Init(file.Pass(), language);
custom_dictionary_.Init(custom_words);
}
bool SpellCheck::SpellCheckWord(
const base::char16* in_word,
int in_word_len,
int tag,
int* misspelling_start,
int* misspelling_len,
std::vector<base::string16>* optional_suggestions) {
DCHECK(in_word_len >= 0);
DCHECK(misspelling_start && misspelling_len) << "Out vars must be given.";
// Do nothing if we need to delay initialization. (Rather than blocking,
// report the word as correctly spelled.)
if (InitializeIfNeeded())
return true;
return spellcheck_.SpellCheckWord(in_word, in_word_len,
tag,
misspelling_start, misspelling_len,
optional_suggestions);
}
bool SpellCheck::SpellCheckParagraph(
const base::string16& text,
WebVector<WebTextCheckingResult>* results) {
#if !defined(OS_MACOSX)
// Mac has its own spell checker, so this method will not be used.
DCHECK(results);
std::vector<WebTextCheckingResult> textcheck_results;
size_t length = text.length();
size_t offset = 0;
// Spellcheck::SpellCheckWord() automatically breaks text into words and
// checks the spellings of the extracted words. This function sets the
// position and length of the first misspelled word and returns false when
// the text includes misspelled words. Therefore, we just repeat calling the
// function until it returns true to check the whole text.
int misspelling_start = 0;
int misspelling_length = 0;
while (offset <= length) {
if (SpellCheckWord(&text[offset],
length - offset,
0,
&misspelling_start,
&misspelling_length,
NULL)) {
results->assign(textcheck_results);
return true;
}
if (!custom_dictionary_.SpellCheckWord(
text, misspelling_start + offset, misspelling_length)) {
base::string16 replacement;
textcheck_results.push_back(WebTextCheckingResult(
blink::WebTextDecorationTypeSpelling,
misspelling_start + offset,
misspelling_length,
replacement));
}
offset += misspelling_start + misspelling_length;
}
results->assign(textcheck_results);
return false;
#else
// This function is only invoked for spell checker functionality that runs
// on the render thread. OSX builds don't have that.
NOTREACHED();
return true;
#endif
}
base::string16 SpellCheck::GetAutoCorrectionWord(const base::string16& word,
int tag) {
base::string16 autocorrect_word;
if (!auto_spell_correct_turned_on_)
return autocorrect_word; // Return the empty string.
int word_length = static_cast<int>(word.size());
if (word_length < 2 ||
word_length > chrome::spellcheck_common::kMaxAutoCorrectWordSize)
return autocorrect_word;
if (InitializeIfNeeded())
return autocorrect_word;
base::char16 misspelled_word[
chrome::spellcheck_common::kMaxAutoCorrectWordSize + 1];
const base::char16* word_char = word.c_str();
for (int i = 0; i <= chrome::spellcheck_common::kMaxAutoCorrectWordSize;
++i) {
if (i >= word_length)
misspelled_word[i] = 0;
else
misspelled_word[i] = word_char[i];
}
// Swap adjacent characters and spellcheck.
int misspelling_start, misspelling_len;
for (int i = 0; i < word_length - 1; i++) {
// Swap.
std::swap(misspelled_word[i], misspelled_word[i + 1]);
// Check spelling.
misspelling_start = misspelling_len = 0;
SpellCheckWord(misspelled_word, word_length, tag, &misspelling_start,
&misspelling_len, NULL);
// Make decision: if only one swap produced a valid word, then we want to
// return it. If we found two or more, we don't do autocorrection.
if (misspelling_len == 0) {
if (autocorrect_word.empty()) {
autocorrect_word.assign(misspelled_word);
} else {
autocorrect_word.clear();
break;
}
}
// Restore the swapped characters.
std::swap(misspelled_word[i], misspelled_word[i + 1]);
}
return autocorrect_word;
}
#if !defined(OS_MACOSX) // OSX uses its own spell checker
void SpellCheck::RequestTextChecking(
const base::string16& text,
blink::WebTextCheckingCompletion* completion) {
// Clean up the previous request before starting a new request.
if (pending_request_param_.get())
pending_request_param_->completion()->didCancelCheckingText();
pending_request_param_.reset(new SpellcheckRequest(
text, completion));
// We will check this text after we finish loading the hunspell dictionary.
if (InitializeIfNeeded())
return;
PostDelayedSpellCheckTask(pending_request_param_.release());
}
#endif
bool SpellCheck::InitializeIfNeeded() {
return spellcheck_.InitializeIfNeeded();
}
#if !defined(OS_MACOSX) // OSX doesn't have |pending_request_param_|
void SpellCheck::PostDelayedSpellCheckTask(SpellcheckRequest* request) {
if (!request)
return;
base::MessageLoopProxy::current()->PostTask(FROM_HERE,
base::Bind(&SpellCheck::PerformSpellCheck,
AsWeakPtr(),
base::Owned(request)));
}
#endif
#if !defined(OS_MACOSX) // Mac uses its native engine instead.
void SpellCheck::PerformSpellCheck(SpellcheckRequest* param) {
DCHECK(param);
if (!spellcheck_.IsEnabled()) {
param->completion()->didCancelCheckingText();
} else {
WebVector<blink::WebTextCheckingResult> results;
SpellCheckParagraph(param->text(), &results);
param->completion()->didFinishCheckingText(results);
}
}
#endif
void SpellCheck::CreateTextCheckingResults(
ResultFilter filter,
int line_offset,
const base::string16& line_text,
const std::vector<SpellCheckResult>& spellcheck_results,
WebVector<WebTextCheckingResult>* textcheck_results) {
// Double-check misspelled words with our spellchecker and attach grammar
// markers to them if our spellchecker tells they are correct words, i.e. they
// are probably contextually-misspelled words.
const base::char16* text = line_text.c_str();
std::vector<WebTextCheckingResult> list;
for (size_t i = 0; i < spellcheck_results.size(); ++i) {
SpellCheckResult::Decoration decoration = spellcheck_results[i].decoration;
int word_location = spellcheck_results[i].location;
int word_length = spellcheck_results[i].length;
int misspelling_start = 0;
int misspelling_length = 0;
if (decoration == SpellCheckResult::SPELLING &&
filter == USE_NATIVE_CHECKER) {
if (SpellCheckWord(text + word_location, word_length, 0,
&misspelling_start, &misspelling_length, NULL)) {
decoration = SpellCheckResult::GRAMMAR;
}
}
if (!custom_dictionary_.SpellCheckWord(
line_text, word_location, word_length)) {
list.push_back(WebTextCheckingResult(
static_cast<WebTextDecorationType>(decoration),
word_location + line_offset,
word_length,
spellcheck_results[i].replacement,
spellcheck_results[i].hash));
}
}
textcheck_results->assign(list);
}