summaryrefslogtreecommitdiffstats
path: root/chrome
diff options
context:
space:
mode:
authorestade@chromium.org <estade@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-11-06 03:05:46 +0000
committerestade@chromium.org <estade@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-11-06 03:05:46 +0000
commit85c55dcd717445cd3763b5c94f9902b4cdd194b0 (patch)
tree2deea721cfac202e3eb8556f66a4cf317a331288 /chrome
parentf1a8b962f0a6f1deb6c8c05a3f86d541e2ba61dd (diff)
downloadchromium_src-85c55dcd717445cd3763b5c94f9902b4cdd194b0.zip
chromium_src-85c55dcd717445cd3763b5c94f9902b4cdd194b0.tar.gz
chromium_src-85c55dcd717445cd3763b5c94f9902b4cdd194b0.tar.bz2
Move the spellchecker to the renderer.
The motivation is that this removes the sync IPC on every call to the spellchecker. Also, currently we spellcheck in the IO thread, which frequently needs to go to disk (in particular, the entire spellcheck dictionary starts paged out), so this will block just the single renderer when that happens, rather than the whole IO thread. This breaks the SpellChecker class into two new classes. 1) On the browser side, we have SpellCheckHost. This class handles browser-wide tasks, such as keeping the custom words list in sync with the on-disk custom words dictionary, downloading missing dictionaries, etc. On Posix, it also opens the bdic file since the renderer isn't allowed to open files. SpellCheckHost is created and destroyed on the UI thread. It is initialized on the file thread. 2) On the renderer side, SpellChecker2. This class will one day be renamed SpellChecker. It handles actual checking of the words, memory maps the dictionary file, loads hunspell, etc. There is one SpellChecker2 per RenderThread (hence one per render process). My intention is for this patch to move Linux to this new approach, and follow up with ports for Windows (which will involve passing a dictionary file name rather than a file descriptor through to the renderer) and Mac (which will involve adding sync ViewHost IPC callsfor when the platform spellchecker is enabled). Note that anyone using the platform spellchecker rather than Hunspell will get no benefit out of this refactor. There should be no loss of functionality for Linux (or any other platform) in this patch. The following should all still work: - dictionary is loaded lazily - hunspell is initialized lazily, per renderer - language changes work. - Dynamic downloading of new dictionaries - auto spell correct works (as well as toggling it). - disabling spellcheck works. - custom words work (including adding in one renderer and immediately having it take effect in other renderers, for certain values of "immediate") TODO: - move spellchecker unit tests to test SpellCheck2 - add sync IPC for platform spellchecker; port to Mac - add dictionary location fallback; port to Windows - remove SpellChecker classes from browser/ BUG=25677 Review URL: http://codereview.chromium.org/357003 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@31199 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome')
-rw-r--r--chrome/browser/automation/automation_profile_impl.h8
-rw-r--r--chrome/browser/profile.cc68
-rw-r--r--chrome/browser/profile.h33
-rw-r--r--chrome/browser/renderer_host/browser_render_process_host.cc84
-rw-r--r--chrome/browser/renderer_host/browser_render_process_host.h19
-rw-r--r--chrome/browser/spellcheck_host.cc278
-rw-r--r--chrome/browser/spellcheck_host.h103
-rw-r--r--chrome/browser/spellchecker.cc1
-rw-r--r--chrome/browser/spellchecker_mac.mm2
-rw-r--r--chrome/browser/tab_contents/render_view_context_menu.cc17
-rwxr-xr-xchrome/chrome.gyp9
-rw-r--r--chrome/common/notification_type.h10
-rw-r--r--chrome/common/render_messages_internal.h30
-rw-r--r--chrome/renderer/render_thread.cc43
-rw-r--r--chrome/renderer/render_thread.h25
-rw-r--r--chrome/renderer/render_view.cc17
-rw-r--r--chrome/renderer/spellchecker/spellcheck.cc264
-rw-r--r--chrome/renderer/spellchecker/spellcheck.h127
-rw-r--r--chrome/renderer/spellchecker/spellcheck_worditerator.cc274
-rw-r--r--chrome/renderer/spellchecker/spellcheck_worditerator.h183
-rw-r--r--chrome/test/testing_profile.h4
21 files changed, 1594 insertions, 5 deletions
diff --git a/chrome/browser/automation/automation_profile_impl.h b/chrome/browser/automation/automation_profile_impl.h
index ac01b76..54abd4c 100644
--- a/chrome/browser/automation/automation_profile_impl.h
+++ b/chrome/browser/automation/automation_profile_impl.h
@@ -204,6 +204,14 @@ class AutomationProfileImpl : public Profile {
virtual void DeleteSpellChecker() {
return original_profile_->DeleteSpellChecker();
}
+#if defined(SPELLCHECKER_IN_RENDERER)
+ virtual SpellCheckHost* GetSpellCheckHost() {
+ return original_profile_->GetSpellCheckHost();
+ }
+ virtual void ReinitializeSpellCheckHost(bool force) {
+ return original_profile_->ReinitializeSpellCheckHost(force);
+ }
+#endif
virtual WebKitContext* GetWebKitContext() {
return original_profile_->GetWebKitContext();
}
diff --git a/chrome/browser/profile.cc b/chrome/browser/profile.cc
index 0402b57..9c182bb 100644
--- a/chrome/browser/profile.cc
+++ b/chrome/browser/profile.cc
@@ -483,6 +483,16 @@ class OffTheRecordProfileImpl : public Profile,
profile_->DeleteSpellChecker();
}
+#if defined(SPELLCHECKER_IN_RENDERER)
+ virtual SpellCheckHost* GetSpellCheckHost() {
+ return profile_->GetSpellCheckHost();
+ }
+
+ virtual void ReinitializeSpellCheckHost(bool force) {
+ profile_->ReinitializeSpellCheckHost(force);
+ }
+#endif
+
virtual WebKitContext* GetWebKitContext() {
if (!webkit_context_.get())
webkit_context_ = new WebKitContext(GetPath(), true);
@@ -574,6 +584,10 @@ ProfileImpl::ProfileImpl(const FilePath& path)
created_theme_provider_(false),
start_time_(Time::Now()),
spellchecker_(NULL),
+#if defined(OS_LINUX)
+ spellcheck_host_(NULL),
+ spellcheck_host_ready_(false),
+#endif
shutdown_session_service_(false) {
DCHECK(!path.empty()) << "Using an empty path will attempt to write " <<
"profile files to the root directory!";
@@ -745,6 +759,10 @@ ProfileImpl::~ProfileImpl() {
if (history_service_.get())
history_service_->Cleanup();
+#if defined(SPELLCHECKER_IN_RENDERER)
+ if (spellcheck_host_.get())
+ spellcheck_host_->UnsetObserver();
+#endif
DeleteSpellCheckerImpl(false);
if (default_request_context_ == request_context_) {
@@ -1272,6 +1290,47 @@ void ProfileImpl::ReinitializeSpellChecker() {
}
}
+#if defined(SPELLCHECKER_IN_RENDERER)
+SpellCheckHost* ProfileImpl::GetSpellCheckHost() {
+ return spellcheck_host_ready_ ? spellcheck_host_.get() : NULL;
+}
+
+void ProfileImpl::ReinitializeSpellCheckHost(bool force) {
+ // If we are already loading the spellchecker, and this is just a hint to
+ // load the spellchecker, do nothing.
+ if (!force && spellcheck_host_.get())
+ return;
+
+ bool notify = false;
+ if (spellcheck_host_.get()) {
+ spellcheck_host_->UnsetObserver();
+ spellcheck_host_.release();
+ spellcheck_host_ready_ = false;
+ notify = true;
+ }
+
+ PrefService* prefs = GetPrefs();
+ if (prefs->GetBoolean(prefs::kEnableSpellCheck)) {
+ // Retrieve the (perhaps updated recently) dictionary name from preferences.
+ spellcheck_host_ = new SpellCheckHost(this,
+ WideToASCII(prefs->GetString(prefs::kSpellCheckDictionary)),
+ GetRequestContext());
+ spellcheck_host_->AddRef();
+ } else if (notify) {
+ // The spellchecker has been disabled.
+ SpellCheckHostInitialized();
+ }
+}
+
+void ProfileImpl::SpellCheckHostInitialized() {
+ spellcheck_host_ready_ =
+ spellcheck_host_ && spellcheck_host_->bdict_fd().fd != -1;
+ NotificationService::current()->Notify(
+ NotificationType::SPELLCHECK_HOST_REINITIALIZED,
+ Source<Profile>(this), NotificationService::NoDetails());
+}
+#endif
+
void ProfileImpl::NotifySpellCheckerChanged() {
SpellcheckerReinitializedDetails scoped_spellchecker;
scoped_spellchecker.spellchecker = spellchecker_;
@@ -1340,9 +1399,14 @@ void ProfileImpl::Observe(NotificationType type,
PrefService* prefs = Source<PrefService>(source).ptr();
DCHECK(pref_name_in && prefs);
if (*pref_name_in == prefs::kSpellCheckDictionary ||
- *pref_name_in == prefs::kEnableSpellCheck ||
- *pref_name_in == prefs::kEnableAutoSpellCorrect) {
+#if !defined(SPELLCHECKER_IN_RENDERER)
+ *pref_name_in == prefs::kEnableAutoSpellCorrect ||
+#endif
+ *pref_name_in == prefs::kEnableSpellCheck) {
ReinitializeSpellChecker();
+#if defined(SPELLCHECKER_IN_RENDERER)
+ ReinitializeSpellCheckHost(true);
+#endif
}
} else if (NotificationType::THEME_INSTALLED == type) {
Extension* extension = Details<Extension>(details).ptr();
diff --git a/chrome/browser/profile.h b/chrome/browser/profile.h
index b00706d..659e1bd 100644
--- a/chrome/browser/profile.h
+++ b/chrome/browser/profile.h
@@ -14,6 +14,9 @@
#include "base/file_path.h"
#include "base/scoped_ptr.h"
#include "base/timer.h"
+#if defined(SPELLCHECKER_IN_RENDERER)
+#include "chrome/browser/spellcheck_host.h"
+#endif
#include "chrome/browser/web_resource/web_resource_service.h"
#include "chrome/common/notification_registrar.h"
@@ -351,6 +354,16 @@ class Profile {
// memory.
virtual void DeleteSpellChecker() = 0;
+#if defined(SPELLCHECKER_IN_RENDERER)
+ // May return NULL.
+ virtual SpellCheckHost* GetSpellCheckHost() = 0;
+
+ // If |force| is false, and the spellchecker is already initialized (or is in
+ // the process of initializing), then do nothing. Otherwise clobber the
+ // current spellchecker and replace it with a new one.
+ virtual void ReinitializeSpellCheckHost(bool force) = 0;
+#endif
+
// Returns the WebKitContext assigned to this profile.
virtual WebKitContext* GetWebKitContext() = 0;
@@ -394,6 +407,9 @@ class OffTheRecordProfileImpl;
// The default profile implementation.
class ProfileImpl : public Profile,
+#if defined(SPELLCHECKER_IN_RENDERER)
+ public SpellCheckHost::Observer,
+#endif
public NotificationObserver {
public:
virtual ~ProfileImpl();
@@ -454,6 +470,10 @@ class ProfileImpl : public Profile,
virtual void ReinitializeSpellChecker();
virtual SpellChecker* GetSpellChecker();
virtual void DeleteSpellChecker() { DeleteSpellCheckerImpl(true); }
+#if defined(SPELLCHECKER_IN_RENDERER)
+ virtual SpellCheckHost* GetSpellCheckHost();
+ virtual void ReinitializeSpellCheckHost(bool force);
+#endif
virtual WebKitContext* GetWebKitContext();
virtual DesktopNotificationService* GetDesktopNotificationService();
virtual void MarkAsCleanShutdown();
@@ -467,6 +487,11 @@ class ProfileImpl : public Profile,
const NotificationSource& source,
const NotificationDetails& details);
+#if defined(SPELLCHECKER_IN_RENDERER)
+ // SpellCheckHost::Observer implementation.
+ virtual void SpellCheckHostInitialized();
+#endif
+
private:
friend class Profile;
@@ -558,6 +583,14 @@ class ProfileImpl : public Profile,
// thread.
SpellChecker* spellchecker_;
+#if defined(SPELLCHECKER_IN_RENDERER)
+ scoped_refptr<SpellCheckHost> spellcheck_host_;
+
+ // Indicates whether |spellcheck_host_| has told us initialization is
+ // finished.
+ bool spellcheck_host_ready_;
+#endif
+
// Set to true when ShutdownSessionService is invoked. If true
// GetSessionService won't recreate the SessionService.
bool shutdown_session_service_;
diff --git a/chrome/browser/renderer_host/browser_render_process_host.cc b/chrome/browser/renderer_host/browser_render_process_host.cc
index 348f21b..748f6fd 100644
--- a/chrome/browser/renderer_host/browser_render_process_host.cc
+++ b/chrome/browser/renderer_host/browser_render_process_host.cc
@@ -40,6 +40,9 @@
#include "chrome/browser/renderer_host/render_widget_host.h"
#include "chrome/browser/renderer_host/resource_message_filter.h"
#include "chrome/browser/renderer_host/web_cache_manager.h"
+#if defined(SPELLCHECKER_IN_RENDERER)
+#include "chrome/browser/spellcheck_host.h"
+#endif
#include "chrome/browser/spellchecker.h"
#include "chrome/browser/visitedlink_master.h"
#include "chrome/common/chrome_switches.h"
@@ -203,6 +206,16 @@ BrowserRenderProcessHost::BrowserRenderProcessHost(Profile* profile)
registrar_.Add(this, NotificationType::USER_SCRIPTS_UPDATED,
NotificationService::AllSources());
+#if defined(SPELLCHECKER_IN_RENDERER)
+ registrar_.Add(this, NotificationType::SPELLCHECK_HOST_REINITIALIZED,
+ NotificationService::AllSources());
+ registrar_.Add(this, NotificationType::SPELLCHECK_WORD_ADDED,
+ NotificationService::AllSources());
+
+ PrefService* prefs = profile->GetPrefs();
+ prefs->AddPrefObserver(prefs::kEnableAutoSpellCorrect, this);
+#endif
+
visited_link_updater_.reset(new VisitedLinkUpdater());
WebCacheManager::GetInstance()->Add(id());
@@ -215,6 +228,11 @@ BrowserRenderProcessHost::BrowserRenderProcessHost(Profile* profile)
}
BrowserRenderProcessHost::~BrowserRenderProcessHost() {
+#if defined(SPELLCHECKER_IN_RENDERER)
+ PrefService* prefs = profile()->GetPrefs();
+ prefs->RemovePrefObserver(prefs::kEnableAutoSpellCorrect, this);
+#endif
+
WebCacheManager::GetInstance()->Remove(id());
ChildProcessSecurityPolicy::GetInstance()->Remove(id());
@@ -348,6 +366,14 @@ bool BrowserRenderProcessHost::Init(bool is_extensions_process) {
InitVisitedLinks();
InitUserScripts();
InitExtensions();
+#if defined(SPELLCHECKER_IN_RENDERER)
+ // We don't want to initialize the spellchecker unless SpellCheckHost has been
+ // created. In InitSpellChecker(), we know if GetSpellCheckHost() is NULL
+ // then the spellchecker has been turned off, but here, we don't know if
+ // it's been turned off or just not loaded yet.
+ if (profile()->GetSpellCheckHost())
+ InitSpellChecker();
+#endif
if (max_page_id_ != -1)
channel_->Send(new ViewMsg_SetNextPageID(max_page_id_ + 1));
@@ -814,6 +840,10 @@ void BrowserRenderProcessHost::OnMessageReceived(const IPC::Message& msg) {
OnExtensionRemoveListener)
IPC_MESSAGE_HANDLER(ViewHostMsg_ExtensionCloseChannel,
OnExtensionCloseChannel)
+#if defined(SPELLCHECKER_IN_RENDERER)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_SpellChecker_RequestDictionary,
+ OnSpellCheckerRequestDictionary)
+#endif
IPC_MESSAGE_UNHANDLED_ERROR()
IPC_END_MESSAGE_MAP_EX()
@@ -1020,6 +1050,29 @@ void BrowserRenderProcessHost::Observe(NotificationType type,
}
break;
}
+#if defined(SPELLCHECKER_IN_RENDERER)
+ case NotificationType::SPELLCHECK_HOST_REINITIALIZED: {
+ InitSpellChecker();
+ break;
+ }
+ case NotificationType::SPELLCHECK_WORD_ADDED: {
+ AddSpellCheckWord(
+ reinterpret_cast<const Source<SpellCheckHost>*>(&source)->
+ ptr()->last_added_word());
+ break;
+ }
+ case NotificationType::PREF_CHANGED: {
+ std::wstring* pref_name_in = Details<std::wstring>(details).ptr();
+ PrefService* prefs = Source<PrefService>(source).ptr();
+ DCHECK(pref_name_in && prefs);
+ if (*pref_name_in == prefs::kEnableAutoSpellCorrect) {
+ EnableAutoSpellCorrect(
+ prefs->GetBoolean(prefs::kEnableAutoSpellCorrect));
+ break;
+ }
+ // Fall through.
+ }
+#endif
default: {
NOTREACHED();
break;
@@ -1048,3 +1101,34 @@ void BrowserRenderProcessHost::OnExtensionCloseChannel(int port_id) {
profile()->GetExtensionMessageService()->CloseChannel(port_id);
}
}
+
+#if defined(SPELLCHECKER_IN_RENDERER)
+void BrowserRenderProcessHost::OnSpellCheckerRequestDictionary() {
+ // We may have gotten multiple requests from different renderers. We don't
+ // want to initialize multiple times in this case, so we set |force| to false.
+ profile()->ReinitializeSpellCheckHost(false);
+}
+
+void BrowserRenderProcessHost::AddSpellCheckWord(const std::string& word) {
+ channel_->Send(new ViewMsg_SpellChecker_WordAdded(word));
+}
+
+void BrowserRenderProcessHost::InitSpellChecker() {
+ SpellCheckHost* spellcheck_host = profile()->GetSpellCheckHost();
+ if (spellcheck_host) {
+ PrefService* prefs = profile()->GetPrefs();
+ channel_->Send(new ViewMsg_SpellChecker_Init(
+ spellcheck_host->bdict_fd(), spellcheck_host->custom_words(),
+ spellcheck_host->language(),
+ prefs->GetBoolean(prefs::kEnableAutoSpellCorrect)));
+ } else {
+ channel_->Send(new ViewMsg_SpellChecker_Init(
+ base::FileDescriptor(), std::vector<std::string>(), std::string(),
+ false));
+ }
+}
+
+void BrowserRenderProcessHost::EnableAutoSpellCorrect(bool enable) {
+ channel_->Send(new ViewMsg_SpellChecker_EnableAutoSpellCorrect(enable));
+}
+#endif
diff --git a/chrome/browser/renderer_host/browser_render_process_host.h b/chrome/browser/renderer_host/browser_render_process_host.h
index 8005e2c..03b2b73 100644
--- a/chrome/browser/renderer_host/browser_render_process_host.h
+++ b/chrome/browser/renderer_host/browser_render_process_host.h
@@ -146,6 +146,25 @@ class BrowserRenderProcessHost : public RenderProcessHost,
// Returns true if the priority is backgrounded; false otherwise.
void SetBackgrounded(bool boost);
+#if defined(SPELLCHECKER_IN_RENDERER)
+ // The renderer has requested that we initialize its spellchecker. This should
+ // generally only be called once per session, as after the first call, all
+ // future renderers will be passed the initialization information on startup
+ // (or when the dictionary changes in some way).
+ void OnSpellCheckerRequestDictionary();
+
+ // Tell the renderer of a new word that has been added to the custom
+ // dictionary.
+ void AddSpellCheckWord(const std::string& word);
+
+ // Pass the renderer some basic intialization information. Note that the
+ // renderer will not load Hunspell until it needs to.
+ void InitSpellChecker();
+
+ // Tell the renderer that auto spell correction has been enabled/disabled.
+ void EnableAutoSpellCorrect(bool enable);
+#endif
+
NotificationRegistrar registrar_;
// The count of currently visible widgets. Since the host can be a container
diff --git a/chrome/browser/spellcheck_host.cc b/chrome/browser/spellcheck_host.cc
new file mode 100644
index 0000000..91a1a9c
--- /dev/null
+++ b/chrome/browser/spellcheck_host.cc
@@ -0,0 +1,278 @@
+// Copyright (c) 2009 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/browser/spellcheck_host.h"
+
+#include <fcntl.h>
+
+#include "app/l10n_util.h"
+#include "base/file_util.h"
+#include "base/logging.h"
+#include "base/path_service.h"
+#include "base/string_util.h"
+#include "chrome/common/chrome_constants.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/common/notification_service.h"
+#include "googleurl/src/gurl.h"
+
+namespace {
+
+static const struct {
+ // The language.
+ const char* language;
+
+ // The corresponding language and region, used by the dictionaries.
+ const char* language_region;
+} g_supported_spellchecker_languages[] = {
+ // Several languages are not to be included in the spellchecker list:
+ // th-TH, hu-HU, bg-BG, uk-UA
+ {"ca", "ca-ES"},
+ {"cs", "cs-CZ"},
+ {"da", "da-DK"},
+ {"de", "de-DE"},
+ {"el", "el-GR"},
+ {"en-AU", "en-AU"},
+ {"en-GB", "en-GB"},
+ {"en-US", "en-US"},
+ {"es", "es-ES"},
+ {"et", "et-EE"},
+ {"fr", "fr-FR"},
+ {"he", "he-IL"},
+ {"hi", "hi-IN"},
+ {"hr", "hr-HR"},
+ {"id", "id-ID"},
+ {"it", "it-IT"},
+ {"lt", "lt-LT"},
+ {"lv", "lv-LV"},
+ {"nb", "nb-NO"},
+ {"nl", "nl-NL"},
+ {"pl", "pl-PL"},
+ {"pt-BR", "pt-BR"},
+ {"pt-PT", "pt-PT"},
+ {"ro", "ro-RO"},
+ {"ru", "ru-RU"},
+ {"sk", "sk-SK"},
+ {"sl", "sl-SI"},
+ {"sv", "sv-SE"},
+ {"tr", "tr-TR"},
+ {"vi", "vi-VN"},
+};
+
+// This function returns the language-region version of language name.
+// e.g. returns hi-IN for hi.
+std::string GetSpellCheckLanguageRegion(const std::string& input_language) {
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(g_supported_spellchecker_languages);
+ ++i) {
+ if (g_supported_spellchecker_languages[i].language == input_language) {
+ return std::string(
+ g_supported_spellchecker_languages[i].language_region);
+ }
+ }
+
+ return input_language;
+}
+
+FilePath GetVersionedFileName(const std::string& input_language,
+ const FilePath& dict_dir) {
+ // The default dictionary version is 1-2. These versions have been augmented
+ // with additional words found by the translation team.
+ static const char kDefaultVersionString[] = "-1-2";
+
+ // The following dictionaries have either not been augmented with additional
+ // words (version 1-1) or have new words, as well as an upgraded dictionary
+ // as of Feb 2009 (version 1-3).
+ static const struct {
+ // The language input.
+ const char* language;
+
+ // The corresponding version.
+ const char* version;
+ } special_version_string[] = {
+ {"en-AU", "-1-1"},
+ {"en-GB", "-1-1"},
+ {"es-ES", "-1-1"},
+ {"nl-NL", "-1-1"},
+ {"ru-RU", "-1-1"},
+ {"sv-SE", "-1-1"},
+ {"he-IL", "-1-1"},
+ {"el-GR", "-1-1"},
+ {"hi-IN", "-1-1"},
+ {"tr-TR", "-1-1"},
+ {"et-EE", "-1-1"},
+ {"fr-FR", "-1-4"}, // To fix a crash, fr dictionary was updated to 1.4.
+ {"lt-LT", "-1-3"},
+ {"pl-PL", "-1-3"}
+ };
+
+ // Generate the bdict file name using default version string or special
+ // version string, depending on the language.
+ std::string language = GetSpellCheckLanguageRegion(input_language);
+ std::string versioned_bdict_file_name(language + kDefaultVersionString +
+ ".bdic");
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(special_version_string); ++i) {
+ if (language == special_version_string[i].language) {
+ versioned_bdict_file_name =
+ language + special_version_string[i].version + ".bdic";
+ break;
+ }
+ }
+
+ return dict_dir.AppendASCII(versioned_bdict_file_name);
+}
+
+} // namespace
+
+// Constructed on UI thread.
+SpellCheckHost::SpellCheckHost(Observer* observer,
+ const std::string& language,
+ URLRequestContextGetter* request_context_getter)
+ : observer_(observer),
+ language_(language),
+ tried_to_download_(false),
+ request_context_getter_(request_context_getter) {
+ DCHECK(observer_);
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+
+ // TODO(estade): for Windows, we need to fall back to DIR_USER_DATA if
+ // DIR_APP_DICTIONARIES is not writeable.
+ FilePath dict_dir;
+ PathService::Get(chrome::DIR_APP_DICTIONARIES, &dict_dir);
+ bdict_file_ = GetVersionedFileName(language, dict_dir);
+
+ FilePath personal_file_directory;
+ PathService::Get(chrome::DIR_USER_DATA, &personal_file_directory);
+ custom_dictionary_file_ =
+ personal_file_directory.Append(chrome::kCustomDictionaryFileName);
+
+ ChromeThread::PostTask(ChromeThread::FILE, FROM_HERE,
+ NewRunnableMethod(this, &SpellCheckHost::Initialize));
+}
+
+SpellCheckHost::~SpellCheckHost() {
+ if (fd_.fd != -1)
+ close(fd_.fd);
+}
+
+void SpellCheckHost::UnsetObserver() {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+
+ observer_ = NULL;
+}
+
+void SpellCheckHost::AddWord(const std::string& word) {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+
+ custom_words_.push_back(word);
+ ChromeThread::PostTask(ChromeThread::FILE, FROM_HERE,
+ NewRunnableMethod(this,
+ &SpellCheckHost::WriteWordToCustomDictionary, word));
+ NotificationService::current()->Notify(
+ NotificationType::SPELLCHECK_WORD_ADDED,
+ Source<SpellCheckHost>(this), NotificationService::NoDetails());
+}
+
+void SpellCheckHost::Initialize() {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::FILE));
+
+ if (!observer_)
+ return;
+
+ // We set |auto_close| to false because we don't want IPC to close the fd.
+ // We will close it manually in the destructor.
+ fd_ = base::FileDescriptor(open(bdict_file_.value().c_str(), O_RDONLY),
+ false);
+
+ // File didn't exist. Download it.
+ if (fd_.fd == -1 && !tried_to_download_) {
+ DownloadDictionary();
+ return;
+ }
+
+ if (fd_.fd != -1) {
+ // Load custom dictionary.
+ std::string contents;
+ file_util::ReadFileToString(custom_dictionary_file_, &contents);
+ std::vector<std::string> list_of_words;
+ SplitString(contents, '\n', &list_of_words);
+ for (size_t i = 0; i < list_of_words.size(); ++i)
+ custom_words_.push_back(list_of_words[i]);
+ }
+
+ ChromeThread::PostTask(ChromeThread::UI, FROM_HERE,
+ NewRunnableMethod(this,
+ &SpellCheckHost::InformObserverOfInitialization));
+}
+
+void SpellCheckHost::InformObserverOfInitialization() {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+
+ if (observer_)
+ observer_->SpellCheckHostInitialized();
+}
+
+void SpellCheckHost::DownloadDictionary() {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::FILE));
+
+ // Determine URL of file to download.
+ static const char kDownloadServerUrl[] =
+ "http://cache.pack.google.com/edgedl/chrome/dict/";
+ GURL url = GURL(std::string(kDownloadServerUrl) + WideToUTF8(
+ l10n_util::ToLower(bdict_file_.BaseName().ToWStringHack())));
+ fetcher_.reset(new URLFetcher(url, URLFetcher::GET, this));
+ fetcher_->set_request_context(request_context_getter_);
+ tried_to_download_ = true;
+ fetcher_->Start();
+}
+
+void SpellCheckHost::WriteWordToCustomDictionary(const std::string& word) {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::FILE));
+
+ // Stored in UTF-8.
+ std::string word_to_add(word + "\n");
+ FILE* f = file_util::OpenFile(custom_dictionary_file_, "a+");
+ if (f != NULL)
+ fputs(word_to_add.c_str(), f);
+ file_util::CloseFile(f);
+}
+
+void SpellCheckHost::OnURLFetchComplete(const URLFetcher* source,
+ const GURL& url,
+ const URLRequestStatus& status,
+ int response_code,
+ const ResponseCookies& cookies,
+ const std::string& data) {
+ DCHECK(source);
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::FILE));
+
+ if ((response_code / 100) != 2) {
+ // Initialize will not try to download the file a second time.
+ LOG(ERROR) << "Failure to download dictionary.";
+ Initialize();
+ return;
+ }
+
+ // Basic sanity check on the dictionary.
+ // There's the small chance that we might see a 200 status code for a body
+ // that represents some form of failure.
+ if (data.size() < 4 || data[0] != 'B' || data[1] != 'D' || data[2] != 'i' ||
+ data[3] != 'c') {
+ LOG(ERROR) << "Failure to download dictionary.";
+ Initialize();
+ return;
+ }
+
+ size_t bytes_written =
+ file_util::WriteFile(bdict_file_, data.data(), data.length());
+ if (bytes_written != data.length()) {
+ LOG(ERROR) << "Failure to save dictionary.";
+ // To avoid trying to load a partially saved dictionary, shortcut the
+ // Initialize() call.
+ ChromeThread::PostTask(ChromeThread::UI, FROM_HERE,
+ NewRunnableMethod(this,
+ &SpellCheckHost::InformObserverOfInitialization));
+ return;
+ }
+
+ Initialize();
+}
diff --git a/chrome/browser/spellcheck_host.h b/chrome/browser/spellcheck_host.h
new file mode 100644
index 0000000..6b0f316
--- /dev/null
+++ b/chrome/browser/spellcheck_host.h
@@ -0,0 +1,103 @@
+// Copyright (c) 2009 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.
+
+#ifndef CHROME_BROWSER_SPELLCHECK_HOST_H_
+#define CHROME_BROWSER_SPELLCHECK_HOST_H_
+
+#include <string>
+#include <vector>
+
+#include "base/file_descriptor_posix.h"
+#include "base/file_path.h"
+#include "base/ref_counted.h"
+#include "chrome/browser/chrome_thread.h"
+#include "chrome/browser/net/url_fetcher.h"
+
+class SpellCheckHost : public base::RefCountedThreadSafe<SpellCheckHost,
+ ChromeThread::DeleteOnFileThread>,
+ public URLFetcher::Delegate {
+ public:
+ class Observer {
+ public:
+ virtual void SpellCheckHostInitialized() = 0;
+ };
+
+ SpellCheckHost(Observer* observer,
+ const std::string& language,
+ URLRequestContextGetter* request_context_getter);
+
+ // Clear |observer_|. Used to prevent calling back to a deleted object.
+ void UnsetObserver();
+
+ // Add the given word to the custom words list and inform renderer of the
+ // update.
+ void AddWord(const std::string& word);
+
+ const base::FileDescriptor& bdict_fd() const { return fd_; };
+
+ const std::vector<std::string>& custom_words() const { return custom_words_; }
+
+ const std::string& last_added_word() const { return custom_words_.back(); }
+
+ const std::string& language() const { return language_; }
+
+ private:
+ // These two classes can destruct us.
+ friend class ChromeThread;
+ friend class DeleteTask<SpellCheckHost>;
+
+ virtual ~SpellCheckHost();
+
+ // Load and parse the custom words dictionary and open the bdic file.
+ // Executed on the file thread.
+ void Initialize();
+
+ // Inform |observer_| that initialization has finished.
+ void InformObserverOfInitialization();
+
+ // If |bdict_file_| is missing, we attempt to download it.
+ void DownloadDictionary();
+
+ // Write a custom dictionary addition to disk.
+ void WriteWordToCustomDictionary(const std::string& word);
+
+ // URLFetcher::Delegate implementation. Called when we finish downloading the
+ // spellcheck dictionary; saves the dictionary to disk.
+ virtual void OnURLFetchComplete(const URLFetcher* source,
+ const GURL& url,
+ const URLRequestStatus& status,
+ int response_code,
+ const ResponseCookies& cookies,
+ const std::string& data);
+
+ // May be NULL.
+ Observer* observer_;
+
+ // The desired location of the dictionary file (whether or not it exists yet).
+ FilePath bdict_file_;
+
+ // The location of the custom words file.
+ FilePath custom_dictionary_file_;
+
+ // The language of the dictionary file.
+ std::string language_;
+
+ // On POSIX, the file descriptor for the dictionary file.
+ base::FileDescriptor fd_;
+
+ // In-memory cache of the custom words file.
+ std::vector<std::string> custom_words_;
+
+ // We don't want to attempt to download a missing dictionary file more than
+ // once.
+ bool tried_to_download_;
+
+ // Used for downloading the dictionary file.
+ URLRequestContextGetter* request_context_getter_;
+
+ // Used for downloading the dictionary file.
+ scoped_ptr<URLFetcher> fetcher_;
+};
+
+#endif // CHROME_BROWSER_SPELLCHECK_HOST_H_
diff --git a/chrome/browser/spellchecker.cc b/chrome/browser/spellchecker.cc
index 856deac..903cdb9 100644
--- a/chrome/browser/spellchecker.cc
+++ b/chrome/browser/spellchecker.cc
@@ -312,6 +312,7 @@ std::string SpellChecker::GetCorrespondingSpellCheckLanguage(
return std::string();
}
+// static
int SpellChecker::GetSpellCheckLanguages(
Profile* profile,
std::vector<std::string>* languages) {
diff --git a/chrome/browser/spellchecker_mac.mm b/chrome/browser/spellchecker_mac.mm
index 99b20a7..72c06e1 100644
--- a/chrome/browser/spellchecker_mac.mm
+++ b/chrome/browser/spellchecker_mac.mm
@@ -73,6 +73,7 @@ std::string ConvertLanguageCodeFromMac(NSString* lang_code) {
} // namespace
namespace SpellCheckerPlatform {
+
void GetAvailableLanguages(std::vector<std::string>* spellcheck_languages) {
NSArray* availableLanguages = [[NSSpellChecker sharedSpellChecker]
availableLanguages];
@@ -207,4 +208,3 @@ void CloseDocumentWithTag(int tag) {
}
} // namespace SpellCheckerPlatform
-
diff --git a/chrome/browser/tab_contents/render_view_context_menu.cc b/chrome/browser/tab_contents/render_view_context_menu.cc
index da3af48..41ebc96 100644
--- a/chrome/browser/tab_contents/render_view_context_menu.cc
+++ b/chrome/browser/tab_contents/render_view_context_menu.cc
@@ -20,6 +20,9 @@
#include "chrome/browser/profile.h"
#include "chrome/browser/search_versus_navigate_classifier.h"
#include "chrome/browser/search_engines/template_url_model.h"
+#if defined(SPELLCHECKER_IN_RENDERER)
+#include "chrome/browser/spellcheck_host.h"
+#endif
#include "chrome/browser/spellchecker.h"
#include "chrome/browser/spellchecker_platform_engine.h"
#include "chrome/browser/tab_contents/navigation_entry.h"
@@ -291,7 +294,7 @@ void RenderViewContextMenu::AppendEditableItems() {
l10n_util::GetStringUTF16(
IDS_CONTENT_CONTEXT_CHECK_SPELLING_OF_THIS_FIELD));
- // Add option for showing the spelling panel if the platfrom spellchecker
+ // Add option for showing the spelling panel if the platform spellchecker
// supports it.
if (SpellCheckerPlatform::SpellCheckerAvailable() &&
SpellCheckerPlatform::SpellCheckerProvidesPanel()) {
@@ -720,10 +723,20 @@ void RenderViewContextMenu::ExecuteItemCommand(int id) {
case IDC_CHECK_SPELLING_OF_THIS_FIELD:
source_tab_contents_->render_view_host()->ToggleSpellCheck();
break;
- case IDS_CONTENT_CONTEXT_ADD_TO_DICTIONARY:
+ case IDS_CONTENT_CONTEXT_ADD_TO_DICTIONARY: {
+#if defined(SPELLCHECKER_IN_RENDERER)
+ SpellCheckHost* spellcheck_host = profile_->GetSpellCheckHost();
+ if (!spellcheck_host) {
+ NOTREACHED();
+ break;
+ }
+ spellcheck_host->AddWord(UTF16ToUTF8(params_.misspelled_word));
+#else
source_tab_contents_->render_view_host()->AddToDictionary(
params_.misspelled_word);
+#endif
break;
+ }
case IDS_CONTENT_CONTEXT_LANGUAGE_SETTINGS:
ShowFontsLanguagesWindow(
diff --git a/chrome/chrome.gyp b/chrome/chrome.gyp
index 43711d0..1ac1ef9 100755
--- a/chrome/chrome.gyp
+++ b/chrome/chrome.gyp
@@ -2528,6 +2528,8 @@
],
'sources': [
'browser/net/ssl_config_service_manager_pref.cc',
+ 'browser/spellcheck_host.cc',
+ 'browser/spellcheck_host.h',
],
'sources/': [
# Exclude most of printing.
@@ -3228,6 +3230,7 @@
'chrome_strings',
'../printing/printing.gyp:printing',
'../skia/skia.gyp:skia',
+ '../third_party/hunspell/hunspell.gyp:hunspell',
'../third_party/icu/icu.gyp:icui18n',
'../third_party/icu/icu.gyp:icuuc',
'../third_party/npapi/npapi.gyp:npapi',
@@ -3366,6 +3369,12 @@
'../build/linux/system.gyp:gtk',
'../sandbox/sandbox.gyp:sandbox',
],
+ 'sources': [
+ 'renderer/spellchecker/spellcheck.cc',
+ 'renderer/spellchecker/spellcheck.h',
+ 'renderer/spellchecker/spellcheck_worditerator.cc',
+ 'renderer/spellchecker/spellcheck_worditerator.h',
+ ],
}],
# Windows-specific rules.
['OS=="win"', {
diff --git a/chrome/common/notification_type.h b/chrome/common/notification_type.h
index db22312..f9597e3 100644
--- a/chrome/common/notification_type.h
+++ b/chrome/common/notification_type.h
@@ -551,6 +551,16 @@ class NotificationType {
// profile.
SPELLCHECKER_REINITIALIZED,
+#if defined(SPELLCHECKER_IN_RENDERER)
+ // Sent when SpellCheckHost has been reloaded. The source is the profile,
+ // the details are NoDetails.
+ SPELLCHECK_HOST_REINITIALIZED,
+
+ // Sent when a new word has been added to the custom dictionary. The source
+ // is the SpellCheckHost, the details are NoDetails.
+ SPELLCHECK_WORD_ADDED,
+#endif
+
// Sent when the bookmark bubble is shown for a particular URL. The source
// is the profile, the details the URL.
BOOKMARK_BUBBLE_SHOWN,
diff --git a/chrome/common/render_messages_internal.h b/chrome/common/render_messages_internal.h
index e111866..373a914 100644
--- a/chrome/common/render_messages_internal.h
+++ b/chrome/common/render_messages_internal.h
@@ -829,6 +829,28 @@ IPC_BEGIN_MESSAGES(View)
IPC_MESSAGE_CONTROL1(ViewMsg_SocketStream_Closed,
int /* socket_id */)
+#if defined(SPELLCHECKER_IN_RENDERER)
+ // SpellChecker messages.
+
+ // Passes some initialization params to the renderer's spellchecker. This can
+ // be called directly after startup or in (async) response to a
+ // RequestDictionary ViewHost message.
+ IPC_MESSAGE_CONTROL4(ViewMsg_SpellChecker_Init,
+ base::FileDescriptor /* bdict_file */,
+ std::vector<std::string> /* custom_dict_words */,
+ std::string /* language */,
+ bool /* auto spell correct */)
+
+ // A word has been added to the custom dictionary; update the local custom
+ // word list.
+ IPC_MESSAGE_CONTROL1(ViewMsg_SpellChecker_WordAdded,
+ std::string /* word */)
+
+ // Toggle the auto spell correct functionality.
+ IPC_MESSAGE_CONTROL1(ViewMsg_SpellChecker_EnableAutoSpellCorrect,
+ bool /* enable */)
+#endif
+
IPC_END_MESSAGES(View)
@@ -1980,4 +2002,12 @@ IPC_BEGIN_MESSAGES(ViewHost)
std::string /* challenge string */,
GURL /* URL of requestor */,
std::string /* signed public key and challenge */)
+
+#if defined(SPELLCHECKER_IN_RENDERER)
+ // The renderer has tried to spell check a word, but couldn't because no
+ // dictionary was available to load. Request that the browser find an
+ // appropriate dictionary and return it.
+ IPC_MESSAGE_CONTROL0(ViewHostMsg_SpellChecker_RequestDictionary)
+#endif
+
IPC_END_MESSAGES(ViewHost)
diff --git a/chrome/renderer/render_thread.cc b/chrome/renderer/render_thread.cc
index 244e61f..e22e310 100644
--- a/chrome/renderer/render_thread.cc
+++ b/chrome/renderer/render_thread.cc
@@ -51,6 +51,9 @@
#include "chrome/renderer/renderer_webkitclient_impl.h"
#include "chrome/renderer/renderer_web_database_observer.h"
#include "chrome/renderer/socket_stream_dispatcher.h"
+#if defined(SPELLCHECKER_IN_RENDERER)
+#include "chrome/renderer/spellchecker/spellcheck.h"
+#endif
#include "chrome/renderer/user_script_slave.h"
#include "ipc/ipc_message.h"
#include "third_party/tcmalloc/tcmalloc/src/google/malloc_extension.h"
@@ -152,6 +155,9 @@ void RenderThread::Init() {
AddFilter(devtools_agent_filter_.get());
db_message_filter_ = new DBMessageFilter();
AddFilter(db_message_filter_.get());
+#if defined(SPELLCHECKER_IN_RENDERER)
+ spellchecker_.reset(new SpellCheck());
+#endif
#if defined(OS_POSIX)
suicide_on_channel_error_filter_ = new SuicideOnChannelErrorFilter;
@@ -331,6 +337,14 @@ void RenderThread::OnControlMessageReceived(const IPC::Message& msg) {
IPC_MESSAGE_HANDLER(ViewMsg_SetIPCLoggingEnabled,
OnSetIPCLoggingEnabled)
#endif
+#if defined(SPELLCHECKER_IN_RENDERER)
+ IPC_MESSAGE_HANDLER(ViewMsg_SpellChecker_Init,
+ OnInitSpellChecker)
+ IPC_MESSAGE_HANDLER(ViewMsg_SpellChecker_WordAdded,
+ OnSpellCheckWordAdded)
+ IPC_MESSAGE_HANDLER(ViewMsg_SpellChecker_EnableAutoSpellCorrect,
+ OnSpellCheckEnableAutoSpellCorrect)
+#endif
IPC_END_MESSAGE_MAP()
}
@@ -430,6 +444,12 @@ void RenderThread::SetCacheMode(bool enabled) {
Send(new ViewHostMsg_SetCacheMode(enabled));
}
+#if defined(SPELLCHECKER_IN_RENDERER)
+void RenderThread::RequestSpellCheckDictionary() {
+ Send(new ViewHostMsg_SpellChecker_RequestDictionary);
+}
+#endif
+
static void* CreateHistogram(
const char *name, int min, int max, size_t buckets) {
Histogram* histogram = new Histogram(name, min, max, buckets);
@@ -580,6 +600,10 @@ void RenderThread::OnExtensionMessageInvoke(const std::string& function_name,
}
void RenderThread::OnPurgeMemory() {
+#if defined(SPELLCHECKER_IN_RENDERER)
+ spellchecker_.reset(new SpellCheck());
+#endif
+
EnsureWebKitInitialized();
// Clear the object cache (as much as possible; some live objects cannot be
@@ -620,3 +644,22 @@ void RenderThread::OnPurgePluginListCache(bool reload_pages) {
WebKit::resetPluginCache(reload_pages);
plugin_refresh_allowed_ = true;
}
+
+#if defined(SPELLCHECKER_IN_RENDERER)
+void RenderThread::OnInitSpellChecker(
+ const base::FileDescriptor& bdict_fd,
+ const std::vector<std::string>& custom_words,
+ const std::string& language,
+ bool auto_spell_correct) {
+ spellchecker_->Init(bdict_fd, custom_words, language);
+ spellchecker_->EnableAutoSpellCorrect(auto_spell_correct);
+}
+
+void RenderThread::OnSpellCheckWordAdded(const std::string& word) {
+ spellchecker_->WordAdded(word);
+}
+
+void RenderThread::OnSpellCheckEnableAutoSpellCorrect(bool enable) {
+ spellchecker_->EnableAutoSpellCorrect(enable);
+}
+#endif
diff --git a/chrome/renderer/render_thread.h b/chrome/renderer/render_thread.h
index 249c57a..dece3ee 100644
--- a/chrome/renderer/render_thread.h
+++ b/chrome/renderer/render_thread.h
@@ -10,6 +10,7 @@
#include "app/gfx/native_widget_types.h"
#include "base/shared_memory.h"
+#include "base/string16.h"
#include "base/task.h"
#include "build/build_config.h"
#include "chrome/common/child_thread.h"
@@ -28,6 +29,7 @@ class RenderDnsMaster;
class RendererHistogram;
class RendererWebDatabaseObserver;
class RendererWebKitClientImpl;
+class SpellCheck;
class SkBitmap;
class SocketStreamDispatcher;
class UserScriptSlave;
@@ -121,6 +123,12 @@ class RenderThread : public RenderThreadBase,
return socket_stream_dispatcher_.get();
}
+#if defined(SPELLCHECKER_IN_RENDERER)
+ SpellCheck* spellchecker() const {
+ return spellchecker_.get();
+ }
+#endif
+
bool plugin_refresh_allowed() const { return plugin_refresh_allowed_; }
// Do DNS prefetch resolution of a hostname.
@@ -139,6 +147,11 @@ class RenderThread : public RenderThreadBase,
// Sends a message to the browser to enable or disable the disk cache.
void SetCacheMode(bool enabled);
+#if defined(SPELLCHECKER_IN_RENDERER)
+ // Send a message to the browser to request a spellcheck dictionary.
+ void RequestSpellCheckDictionary();
+#endif
+
private:
virtual void OnControlMessageReceived(const IPC::Message& msg);
@@ -185,6 +198,15 @@ class RenderThread : public RenderThreadBase,
void OnPurgeMemory();
void OnPurgePluginListCache(bool reload_pages);
+#if defined(SPELLCHECKER_IN_RENDERER)
+ void OnInitSpellChecker(const base::FileDescriptor& bdict_fd,
+ const std::vector<std::string>& custom_words,
+ const std::string& language,
+ bool auto_spell_correct);
+ void OnSpellCheckWordAdded(const std::string& word);
+ void OnSpellCheckEnableAutoSpellCorrect(bool enable);
+#endif
+
// Gather usage statistics from the in-memory cache and inform our host.
// These functions should be call periodically so that the host can make
// decisions about how to allocation resources using current information.
@@ -208,6 +230,9 @@ class RenderThread : public RenderThreadBase,
scoped_ptr<WebKit::WebStorageEventDispatcher> dom_storage_event_dispatcher_;
scoped_ptr<SocketStreamDispatcher> socket_stream_dispatcher_;
scoped_ptr<RendererWebDatabaseObserver> renderer_web_database_observer_;
+#if defined(SPELLCHECKER_IN_RENDERER)
+ scoped_ptr<SpellCheck> spellchecker_;
+#endif
// Used on the renderer and IPC threads.
scoped_refptr<DBMessageFilter> db_message_filter_;
diff --git a/chrome/renderer/render_view.cc b/chrome/renderer/render_view.cc
index fc74190..6629a65 100644
--- a/chrome/renderer/render_view.cc
+++ b/chrome/renderer/render_view.cc
@@ -52,6 +52,9 @@
#include "chrome/renderer/plugin_channel_host.h"
#include "chrome/renderer/print_web_view_helper.h"
#include "chrome/renderer/render_process.h"
+#if defined(SPELLCHECKER_IN_RENDERER)
+#include "chrome/renderer/spellchecker/spellcheck.h"
+#endif
#include "chrome/renderer/user_script_slave.h"
#include "chrome/renderer/visitedlink_slave.h"
#include "chrome/renderer/webplugin_delegate_pepper.h"
@@ -1498,8 +1501,16 @@ void RenderView::spellCheck(const WebString& text,
int& misspelled_offset,
int& misspelled_length) {
EnsureDocumentTag();
+
+#if defined(SPELLCHECKER_IN_RENDERER)
+ string16 word(text);
+ RenderThread::current()->spellchecker()->SpellCheckWord(
+ word.c_str(), word.size(), document_tag_,
+ &misspelled_offset, &misspelled_length, NULL);
+#else
Send(new ViewHostMsg_SpellCheck(routing_id_, text, document_tag_,
&misspelled_offset, &misspelled_length));
+#endif
}
WebString RenderView::autoCorrectWord(const WebKit::WebString& word) {
@@ -1507,8 +1518,14 @@ WebString RenderView::autoCorrectWord(const WebKit::WebString& word) {
const CommandLine& command_line = *CommandLine::ForCurrentProcess();
if (command_line.HasSwitch(switches::kExperimentalSpellcheckerFeatures)) {
EnsureDocumentTag();
+#if defined(SPELLCHECKER_IN_RENDERER)
+ autocorrect_word =
+ RenderThread::current()->spellchecker()->GetAutoCorrectionWord(
+ word, document_tag_);
+#else
Send(new ViewHostMsg_GetAutoCorrectWord(
routing_id_, word, document_tag_, &autocorrect_word));
+#endif
}
return autocorrect_word;
}
diff --git a/chrome/renderer/spellchecker/spellcheck.cc b/chrome/renderer/spellchecker/spellcheck.cc
new file mode 100644
index 0000000..a565b08
--- /dev/null
+++ b/chrome/renderer/spellchecker/spellcheck.cc
@@ -0,0 +1,264 @@
+// Copyright (c) 2009 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/file_util.h"
+#include "base/histogram.h"
+#include "base/time.h"
+#include "chrome/renderer/render_thread.h"
+#include "third_party/hunspell/src/hunspell/hunspell.hxx"
+
+static const int kMaxAutoCorrectWordSize = 8;
+static const int kMaxSuggestions = 5;
+
+using base::TimeTicks;
+
+SpellCheck::SpellCheck()
+ : auto_spell_correct_turned_on_(false),
+ // TODO(estade): initialize this properly.
+ is_using_platform_spelling_engine_(false),
+ initialized_(false) {
+ // Wait till we check the first word before doing any initializing.
+}
+
+SpellCheck::~SpellCheck() {
+}
+
+void SpellCheck::Init(const base::FileDescriptor& fd,
+ const std::vector<std::string>& custom_words,
+ const std::string language) {
+ initialized_ = true;
+ hunspell_.reset();
+ bdict_file_.reset();
+ fd_ = fd;
+ character_attributes_.SetDefaultLanguage(language);
+
+ custom_words_.insert(custom_words_.end(),
+ custom_words.begin(), custom_words.end());
+
+ // We delay the actual initialization of hunspell until it is needed.
+}
+
+bool SpellCheck::SpellCheckWord(
+ const char16* in_word,
+ int in_word_len,
+ int tag,
+ int* misspelling_start,
+ int* misspelling_len,
+ std::vector<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;
+
+ // Do nothing if spell checking is disabled.
+ if (initialized_ && fd_.fd == -1)
+ return true;
+
+ *misspelling_start = 0;
+ *misspelling_len = 0;
+ if (in_word_len == 0)
+ return true; // No input means always spelled correctly.
+
+ SpellcheckWordIterator word_iterator;
+ string16 word;
+ int word_start;
+ int word_length;
+ word_iterator.Initialize(&character_attributes_, in_word, in_word_len, true);
+ while (word_iterator.GetNextWord(&word, &word_start, &word_length)) {
+ // Found a word (or a contraction) that the spellchecker can check the
+ // spelling of.
+ if (CheckSpelling(word, tag))
+ continue;
+
+ // If the given word is a concatenated word of two or more valid words
+ // (e.g. "hello:hello"), we should treat it as a valid word.
+ if (IsValidContraction(word, tag))
+ continue;
+
+ *misspelling_start = word_start;
+ *misspelling_len = word_length;
+
+ // Get the list of suggested words.
+ if (optional_suggestions)
+ FillSuggestionList(word, optional_suggestions);
+ return false;
+ }
+
+ return true;
+}
+
+string16 SpellCheck::GetAutoCorrectionWord(const string16& word, int tag) {
+ 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 > kMaxAutoCorrectWordSize)
+ return autocorrect_word;
+
+ if (InitializeIfNeeded())
+ return autocorrect_word;
+
+ char16 misspelled_word[kMaxAutoCorrectWordSize + 1];
+ const char16* word_char = word.c_str();
+ for (int i = 0; i <= kMaxAutoCorrectWordSize; i++) {
+ if (i >= word_length)
+ misspelled_word[i] = NULL;
+ 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;
+}
+
+void SpellCheck::EnableAutoSpellCorrect(bool turn_on) {
+ auto_spell_correct_turned_on_ = turn_on;
+}
+
+void SpellCheck::WordAdded(const std::string& word) {
+ if (is_using_platform_spelling_engine_)
+ return;
+
+ if (!hunspell_.get()) {
+ // Save it for later---add it when hunspell is initialized.
+ custom_words_.push_back(word);
+ } else {
+ AddWordToHunspell(word);
+ }
+}
+
+void SpellCheck::InitializeHunspell() {
+ if (hunspell_.get())
+ return;
+
+ bdict_file_.reset(new file_util::MemoryMappedFile);
+
+ if (bdict_file_->Initialize(fd_)) {
+ TimeTicks start_time = TimeTicks::Now();
+
+ hunspell_.reset(
+ new Hunspell(bdict_file_->data(), bdict_file_->length()));
+
+ // Add custom words to Hunspell.
+ for (std::vector<std::string>::iterator it = custom_words_.begin();
+ it != custom_words_.end(); ++it) {
+ AddWordToHunspell(*it);
+ }
+
+ DHISTOGRAM_TIMES("Spellcheck.InitTime",
+ TimeTicks::Now() - start_time);
+ }
+}
+
+void SpellCheck::AddWordToHunspell(const std::string& word) {
+ if (!word.empty() && word.length() < MAXWORDUTF8LEN)
+ hunspell_->add(word.c_str());
+}
+
+bool SpellCheck::InitializeIfNeeded() {
+ if (!initialized_) {
+ RenderThread::current()->RequestSpellCheckDictionary();
+ initialized_ = true;
+ return true;
+ }
+
+ // Check if the platform spellchecker is being used.
+ if (!is_using_platform_spelling_engine_ && fd_.fd != -1) {
+ // If it isn't, init hunspell.
+ InitializeHunspell();
+ }
+
+ return false;
+}
+
+// When called, relays the request to check the spelling to the proper
+// backend, either hunspell or a platform-specific backend.
+bool SpellCheck::CheckSpelling(const string16& word_to_check, int tag) {
+ bool word_correct = false;
+
+ if (is_using_platform_spelling_engine_) {
+ // TODO(estade): sync IPC to browser.
+ word_correct = true;
+ } else {
+ std::string word_to_check_utf8(UTF16ToUTF8(word_to_check));
+ // Hunspell shouldn't let us exceed its max, but check just in case
+ if (word_to_check_utf8.length() < MAXWORDUTF8LEN) {
+ // |hunspell_->spell| returns 0 if the word is spelled correctly and
+ // non-zero otherwsie.
+ word_correct = (hunspell_->spell(word_to_check_utf8.c_str()) != 0);
+ }
+ }
+
+ return word_correct;
+}
+
+void SpellCheck::FillSuggestionList(
+ const string16& wrong_word,
+ std::vector<string16>* optional_suggestions) {
+ if (is_using_platform_spelling_engine_) {
+ // TODO(estade): sync IPC to browser.
+ return;
+ }
+ char** suggestions;
+ int number_of_suggestions =
+ hunspell_->suggest(&suggestions, UTF16ToUTF8(wrong_word).c_str());
+
+ // Populate the vector of WideStrings.
+ for (int i = 0; i < number_of_suggestions; i++) {
+ if (i < kMaxSuggestions)
+ optional_suggestions->push_back(UTF8ToUTF16(suggestions[i]));
+ free(suggestions[i]);
+ }
+ if (suggestions != NULL)
+ free(suggestions);
+}
+
+// Returns whether or not the given string is a valid contraction.
+// This function is a fall-back when the SpellcheckWordIterator class
+// returns a concatenated word which is not in the selected dictionary
+// (e.g. "in'n'out") but each word is valid.
+bool SpellCheck::IsValidContraction(const string16& contraction, int tag) {
+ SpellcheckWordIterator word_iterator;
+ word_iterator.Initialize(&character_attributes_, contraction.c_str(),
+ contraction.length(), false);
+
+ string16 word;
+ int word_start;
+ int word_length;
+ while (word_iterator.GetNextWord(&word, &word_start, &word_length)) {
+ if (!CheckSpelling(word, tag))
+ return false;
+ }
+ return true;
+}
diff --git a/chrome/renderer/spellchecker/spellcheck.h b/chrome/renderer/spellchecker/spellcheck.h
new file mode 100644
index 0000000..3b2e19d
--- /dev/null
+++ b/chrome/renderer/spellchecker/spellcheck.h
@@ -0,0 +1,127 @@
+// Copyright (c) 2009 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.
+
+#ifndef CHROME_RENDERER_SPELLCHECKER_SPELLCHECKER_H_
+#define CHROME_RENDERER_SPELLCHECKER_SPELLCHECKER_H_
+
+#include <queue>
+#include <string>
+#include <vector>
+
+#include "app/l10n_util.h"
+#include "base/file_descriptor_posix.h"
+#include "base/string16.h"
+#include "base/time.h"
+#include "chrome/renderer/spellchecker/spellcheck_worditerator.h"
+#include "unicode/uscript.h"
+
+class Hunspell;
+
+namespace base {
+class FileDescriptor;
+}
+
+namespace file_util {
+class MemoryMappedFile;
+}
+
+class SpellCheck {
+ public:
+ SpellCheck();
+
+ ~SpellCheck();
+
+ void Init(const base::FileDescriptor& bdict_fd,
+ const std::vector<std::string>& custom_words,
+ const std::string language);
+
+ // SpellCheck a word.
+ // Returns true if spelled correctly, false otherwise.
+ // If the spellchecker failed to initialize, always returns true.
+ // The |tag| parameter should either be a unique identifier for the document
+ // that the word came from (if the current platform requires it), or 0.
+ // In addition, finds the suggested words for a given word
+ // and puts them into |*optional_suggestions|.
+ // If the word is spelled correctly, the vector is empty.
+ // If optional_suggestions is NULL, suggested words will not be looked up.
+ // Note that Doing suggest lookups can be slow.
+ bool SpellCheckWord(const char16* in_word,
+ int in_word_len,
+ int tag,
+ int* misspelling_start,
+ int* misspelling_len,
+ std::vector<string16>* optional_suggestions);
+
+ // Find a possible correctly spelled word for a misspelled word. Computes an
+ // empty string if input misspelled word is too long, there is ambiguity, or
+ // the correct spelling cannot be determined.
+ string16 GetAutoCorrectionWord(const string16& word, int tag);
+
+ // Turn auto spell correct support ON or OFF.
+ // |turn_on| = true means turn ON; false means turn OFF.
+ void EnableAutoSpellCorrect(bool turn_on);
+
+ // Add a word to the custom list. This may be called before or after
+ // |hunspell_| has been initialized.
+ void WordAdded(const std::string& word);
+
+ private:
+ // Initializes the Hunspell dictionary, or does nothing if |hunspell_| is
+ // non-null. This blocks.
+ void InitializeHunspell();
+
+ // If there is no dictionary file, then this requests one from the browser
+ // and does not block. In this case it returns true.
+ // If there is a dictionary file, but Hunspell has not been loaded, then
+ // this loads Hunspell.
+ // If Hunspell is already loaded, this does nothing. In both the latter cases
+ // it returns false, meaning that it is OK to continue spellchecking.
+ bool InitializeIfNeeded();
+
+ // When called, relays the request to check the spelling to the proper
+ // backend, either hunspell or a platform-specific backend.
+ bool CheckSpelling(const string16& word_to_check, int tag);
+
+ // When called, relays the request to fill the list with suggestions to
+ // the proper backend, either hunspell or a platform-specific backend.
+ void FillSuggestionList(const string16& wrong_word,
+ std::vector<string16>* optional_suggestions);
+
+ // Returns whether or not the given word is a contraction of valid words
+ // (e.g. "word:word").
+ bool IsValidContraction(const string16& word, int tag);
+
+ // Add the given custom word to |hunspell_|.
+ void AddWordToHunspell(const std::string& word);
+
+ // We memory-map the BDict file.
+ scoped_ptr<file_util::MemoryMappedFile> bdict_file_;
+
+ // The hunspell dictionary in use.
+ scoped_ptr<Hunspell> hunspell_;
+
+ base::FileDescriptor fd_;
+ std::vector<std::string> custom_words_;
+
+ // Represents character attributes used for filtering out characters which
+ // are not supported by this SpellCheck object.
+ SpellcheckCharAttribute character_attributes_;
+
+ // Remember state for auto spell correct.
+ bool auto_spell_correct_turned_on_;
+
+ // True if a platform-specific spellchecking engine is being used,
+ // and False if hunspell is being used.
+ bool is_using_platform_spelling_engine_;
+
+ // This flags whether we have ever been initialized, or have asked the browser
+ // for a dictionary. The value indicates whether we should request a
+ // dictionary from the browser when the render view asks us to check the
+ // spelling of a word.
+ bool initialized_;
+
+ DISALLOW_COPY_AND_ASSIGN(SpellCheck);
+};
+
+#endif // CHROME_RENDERER_SPELLCHECKER_SPELLCHECKER_H_
diff --git a/chrome/renderer/spellchecker/spellcheck_worditerator.cc b/chrome/renderer/spellchecker/spellcheck_worditerator.cc
new file mode 100644
index 0000000..827d9ee
--- /dev/null
+++ b/chrome/renderer/spellchecker/spellcheck_worditerator.cc
@@ -0,0 +1,274 @@
+// Copyright (c) 2009 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_worditerator.h"
+
+#include <map>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/string_util.h"
+#include "chrome/renderer/spellchecker/spellcheck.h"
+
+#include "third_party/icu/public/common/unicode/normlzr.h"
+#include "third_party/icu/public/common/unicode/schriter.h"
+#include "third_party/icu/public/common/unicode/uchar.h"
+#include "third_party/icu/public/common/unicode/uscript.h"
+#include "third_party/icu/public/common/unicode/uset.h"
+#include "third_party/icu/public/i18n/unicode/ulocdata.h"
+
+SpellcheckCharAttribute::SpellcheckCharAttribute() {
+ InitializeScriptTable();
+
+ // Even though many dictionaries treats numbers and contractions as words and
+ // treats USCRIPT_COMMON characters as word characters, the
+ // SpellcheckWordIterator class treats USCRIPT_COMMON characters as non-word
+ // characters to strictly-distinguish contraction characters from word
+ // characters.
+ SetWordScript(USCRIPT_COMMON, false);
+
+ // Initialize the table of characters used for contractions.
+ // This array consists of the 'Midletter' and 'MidNumLet' characters of the
+ // word-break property list provided by Unicode, Inc.:
+ // http://www.unicode.org/Public/UNIDATA/auxiliary/WordBreakProperty.txt
+ static const UChar32 kMidLetters[] = {
+ L'\x003A', // MidLetter # COLON
+ L'\x00B7', // MidLetter # MIDDLE DOT
+ L'\x0387', // MidLetter # GREEK ANO TELEIA
+ L'\x05F4', // MidLetter # HEBREW PUNCTUATION GERSHAYIM
+ L'\x2027', // MidLetter # HYPHENATION POINT
+ L'\xFE13', // MidLetter # PRESENTATION FORM FOR VERTICAL COLON
+ L'\xFE55', // MidLetter # SMALL COLON
+ L'\xFF1A', // MidLetter # FULLWIDTH COLON
+ L'\x0027', // MidNumLet # APOSTROPHE
+ L'\x002E', // MidNumLet # FULL STOP
+ L'\x2018', // MidNumLet # LEFT SINGLE QUOTATION MARK
+ L'\x2019', // MidNumLet # RIGHT SINGLE QUOTATION MARK
+ L'\x2024', // MidNumLet # ONE DOT LEADER
+ L'\xFE52', // MidNumLet # SMALL FULL STOP
+ L'\xFF07', // MidNumLet # FULLWIDTH APOSTROPHE
+ L'\xFF0E', // MidNumLet # FULLWIDTH FULL STOP
+ };
+ for (size_t i = 0; i < arraysize(kMidLetters); ++i)
+ middle_letters_[kMidLetters[i]] = true;
+}
+
+SpellcheckCharAttribute::~SpellcheckCharAttribute() {
+}
+
+// Sets the default language for this object.
+// This function retrieves the exemplar set to set up the default character
+// attributes.
+void SpellcheckCharAttribute::SetDefaultLanguage(const std::string& language) {
+ UErrorCode status = U_ZERO_ERROR;
+ ULocaleData* locale_data = ulocdata_open(language.c_str(), &status);
+ if (U_FAILURE(status))
+ return;
+
+ // Retrieves the exemplar set of the given language and update the
+ // character-attribute table to treat its characters as word characters.
+ USet* exemplar_set = uset_open(1, 0);
+ ulocdata_getExemplarSet(locale_data, exemplar_set, 0, ULOCDATA_ES_STANDARD,
+ &status);
+ ulocdata_close(locale_data);
+ if (U_SUCCESS(status)) {
+ int length = uset_size(exemplar_set);
+ for (int i = 0; i < length; ++i) {
+ UChar32 character = uset_charAt(exemplar_set, i);
+ SetWordScript(GetScriptCode(character), true);
+ }
+
+ // Many languages use combining characters to input their characters from
+ // keyboards. On the other hand, this exemplar set does not always include
+ // combining characters for such languages.
+ // To treat such combining characters as word characters, we decompose
+ // this exemplar set and treat the decomposed characters as word characters.
+ icu::UnicodeString composed;
+ for (int i = 0; i < length; ++i)
+ composed.append(uset_charAt(exemplar_set, i));
+
+ icu::UnicodeString decomposed;
+ icu::Normalizer::decompose(composed, FALSE, 0, decomposed, status);
+ if (U_SUCCESS(status)) {
+ icu::StringCharacterIterator iterator(decomposed);
+ UChar32 character = iterator.first32();
+ while (character != icu::CharacterIterator::DONE) {
+ SetWordScript(GetScriptCode(character), true);
+ character = iterator.next32();
+ }
+ }
+ }
+ uset_close(exemplar_set);
+}
+
+// Returns whether or not the given character is a character used by the
+// selected dictionary.
+bool SpellcheckCharAttribute::IsWordChar(UChar32 character) const {
+ return IsWordScript(GetScriptCode(character)) && !u_isdigit(character);
+}
+
+// Returns whether or not the given character is a character used by
+// contractions.
+bool SpellcheckCharAttribute::IsContractionChar(UChar32 character) const {
+ std::map<UChar32, bool>::const_iterator iterator;
+ iterator = middle_letters_.find(character);
+ if (iterator == middle_letters_.end())
+ return false;
+ return iterator->second;
+}
+
+// Initializes the mapping table.
+void SpellcheckCharAttribute::InitializeScriptTable() {
+ for (size_t i = 0; i < arraysize(script_attributes_); ++i)
+ script_attributes_[i] = false;
+}
+
+// Retrieves the ICU script code.
+UScriptCode SpellcheckCharAttribute::GetScriptCode(UChar32 character) const {
+ UErrorCode status = U_ZERO_ERROR;
+ UScriptCode script_code = uscript_getScript(character, &status);
+ return U_SUCCESS(status) ? script_code : USCRIPT_INVALID_CODE;
+}
+
+// Updates the mapping table from an ICU script code to its attribute, i.e.
+// whether not a script is used by the selected dictionary.
+void SpellcheckCharAttribute::SetWordScript(const int script_code,
+ bool in_use) {
+ if (script_code < 0 ||
+ static_cast<size_t>(script_code) >= arraysize(script_attributes_))
+ return;
+ script_attributes_[script_code] = in_use;
+}
+
+// Returns whether or not the given script is used by the selected
+// dictionary.
+bool SpellcheckCharAttribute::IsWordScript(
+ const UScriptCode script_code) const {
+ if (script_code < 0 ||
+ static_cast<size_t>(script_code) >= arraysize(script_attributes_))
+ return false;
+ return script_attributes_[script_code];
+}
+
+SpellcheckWordIterator::SpellcheckWordIterator()
+ : word_(NULL),
+ length_(0),
+ position_(0),
+ allow_contraction_(false),
+ attribute_(NULL) {
+}
+
+SpellcheckWordIterator::~SpellcheckWordIterator() {
+}
+
+// Initialize a word-iterator object.
+void SpellcheckWordIterator::Initialize(
+ const SpellcheckCharAttribute* attribute,
+ const char16* word,
+ size_t length,
+ bool allow_contraction) {
+ word_ = word;
+ position_ = 0;
+ length_ = static_cast<int>(length);
+ allow_contraction_ = allow_contraction;
+ attribute_ = attribute;
+}
+
+// Retrieves a word (or a contraction).
+// When a contraction is enclosed with contraction characters (e.g. 'isn't',
+// 'rock'n'roll'), we should discard the beginning and the end of the
+// contraction but we should never split the contraction.
+// To handle this case easily, we should firstly extract a segment consisting
+// of word characters and contraction characters, and discard contraction
+// characters at the beginning and the end of the extracted segment.
+bool SpellcheckWordIterator::GetNextWord(string16* word_string,
+ int* word_start,
+ int* word_length) {
+ word_string->empty();
+ *word_start = 0;
+ *word_length = 0;
+ while (position_ < length_) {
+ int segment_start = 0;
+ int segment_end = 0;
+ GetSegment(&segment_start, &segment_end);
+ TrimSegment(segment_start, segment_end, word_start, word_length);
+ if (*word_length > 0)
+ return Normalize(*word_start, *word_length, word_string);
+ }
+
+ return false;
+}
+
+// Retrieves a segment consisting of word characters (and contraction
+// characters if the |allow_contraction_| value is true).
+// When the current position refers to a non-word character, this function
+// returns a non-empty segment consisting of the character itself. In this
+// case, the TrimSegment() function discards the character and returns an
+// empty word (i.e. |word_length| == 0).
+void SpellcheckWordIterator::GetSegment(int* segment_start,
+ int* segment_end) {
+ int position = position_;
+ while (position < length_) {
+ UChar32 character;
+ U16_NEXT(word_, position, length_, character);
+ if (!attribute_->IsWordChar(character)) {
+ if (!allow_contraction_ || !attribute_->IsContractionChar(character))
+ break;
+ }
+ }
+ *segment_start = position_;
+ *segment_end = position;
+ position_ = position;
+}
+
+// Discards non-word characters at the beginning and the end of the given
+// segment.
+void SpellcheckWordIterator::TrimSegment(int segment_start,
+ int segment_end,
+ int* word_start,
+ int* word_length) const {
+ while (segment_start < segment_end) {
+ UChar32 character;
+ int segment_next = segment_start;
+ U16_NEXT(word_, segment_next, segment_end, character);
+ if (attribute_->IsWordChar(character)) {
+ *word_start = segment_start;
+ break;
+ }
+ segment_start = segment_next;
+ }
+ while (segment_end >= segment_start) {
+ UChar32 character;
+ int segment_prev = segment_end;
+ U16_PREV(word_, segment_start, segment_prev, character);
+ if (attribute_->IsWordChar(character)) {
+ *word_length = segment_end - segment_start;
+ break;
+ }
+ segment_end = segment_prev;
+ }
+}
+
+// Normalizes a non-terminated string into its canonical form so that
+// a spellchecker object can check spellings of words which contain ligatures,
+// full-width letters, etc.
+// USCRIPT_LATIN does not only consists of US-ASCII and ISO/IEC 8859-1, but
+// also consists of ISO/IEC 8859-{2,3,4,9,10}, ligatures, fullwidth latin,
+// etc. For its details, please read the script table in
+// "http://www.unicode.org/Public/UNIDATA/Scripts.txt".
+bool SpellcheckWordIterator::Normalize(int input_start,
+ int input_length,
+ string16* output_string) const {
+ // Unicode Standard Annex #15 "http://www.unicode.org/unicode/reports/tr15/"
+ // does not only write NFKD and NFKC can compose ligatures into their ASCII
+ // alternatives, but also write NFKC keeps accents of characters.
+ // Therefore, NFKC seems to be the best option for hunspell.
+ icu::UnicodeString input(FALSE, &word_[input_start], input_length);
+ UErrorCode status = U_ZERO_ERROR;
+ icu::UnicodeString output;
+ icu::Normalizer::normalize(input, UNORM_NFKC, 0, output, status);
+ if (U_SUCCESS(status))
+ output_string->assign(output.getTerminatedBuffer());
+ return status == U_ZERO_ERROR || status == U_STRING_NOT_TERMINATED_WARNING;
+}
diff --git a/chrome/renderer/spellchecker/spellcheck_worditerator.h b/chrome/renderer/spellchecker/spellcheck_worditerator.h
new file mode 100644
index 0000000..7763314
--- /dev/null
+++ b/chrome/renderer/spellchecker/spellcheck_worditerator.h
@@ -0,0 +1,183 @@
+// Copyright (c) 2009 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.
+
+#ifndef CHROME_RENDERER_SPELLCHECKER_SPELLCHECK_WORDITERATOR_H_
+#define CHROME_RENDERER_SPELLCHECKER_SPELLCHECK_WORDITERATOR_H_
+
+#include <map>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/string16.h"
+
+#include "unicode/uscript.h"
+
+// A class which handles character attributes dependent on a spellchecker and
+// its dictionary.
+// This class is used by the SpellcheckWordIterator class to determine whether
+// or not a character is one used by the spellchecker and its dictinary.
+class SpellcheckCharAttribute {
+ public:
+ SpellcheckCharAttribute();
+
+ ~SpellcheckCharAttribute();
+
+ // Sets the default language of the spell checker. This controls which
+ // characters are considered parts of words of the given language.
+ void SetDefaultLanguage(const std::string& language);
+
+ // Returns whether or not the given character is a character used by the
+ // selected dictionary.
+ // Parameters
+ // * character [in] (UChar32)
+ // Represents a Unicode character to be checked.
+ // Return values
+ // * true
+ // The given character is a word character.
+ // * false
+ // The given character is not a word character.
+ bool IsWordChar(UChar32 character) const;
+
+ // Returns whether or not the given character is a character used by
+ // contractions.
+ // Parameters
+ // * character [in] (UChar32)
+ // Represents a Unicode character to be checked.
+ // Return values
+ // * true
+ // The given character is a character used by contractions.
+ // * false
+ // The given character is not a character used by contractions.
+ bool IsContractionChar(UChar32 character) const;
+
+ private:
+ // Initializes the mapping table.
+ void InitializeScriptTable();
+
+ // Retrieves the ICU script code.
+ UScriptCode GetScriptCode(UChar32 character) const;
+
+ // Updates an entry in the mapping table.
+ void SetWordScript(const int script_code, bool in_use);
+
+ // Returns whether or not the given script is used by the selected
+ // dictionary.
+ bool IsWordScript(const UScriptCode script_code) const;
+
+ private:
+ // Represents a mapping table from a script code to a boolean value
+ // representing whether or not the script is used by the selected dictionary.
+ bool script_attributes_[USCRIPT_CODE_LIMIT];
+
+ // Represents a table of characters used by contractions.
+ std::map<UChar32, bool> middle_letters_;
+
+ DISALLOW_COPY_AND_ASSIGN(SpellcheckCharAttribute);
+};
+
+// A class which implements methods for finding the location of word boundaries
+// used by the Spellchecker class.
+// This class is implemented on the following assumptions:
+// * An input string is encoded in UTF-16 (i.e. it may contain surrogate
+// pairs), and;
+// * The length of a string is the number of UTF-16 characters in the string
+// (i.e. the length of a non-BMP character becomes two).
+class SpellcheckWordIterator {
+ public:
+ SpellcheckWordIterator();
+
+ ~SpellcheckWordIterator();
+
+ // Initializes a word-iterator object.
+ // Parameters
+ // * attribute [in] (const SpellcheckCharAttribute*)
+ // Represents a set of character attributes used for filtering out
+ // non-word characters.
+ // * word [in] (const char16*)
+ // Represents a string from which this object extracts words.
+ // (This string does not have to be NUL-terminated.)
+ // * length [in] (size_t)
+ // Represents the length of the given string, in UTF-16 characters.
+ // This value should not include terminating NUL characters.
+ // * allow_contraction [in] (bool)
+ // Represents a flag to control whether or not this object should split a
+ // possible contraction (e.g. "isn't", "in'n'out", etc.)
+ // Return values
+ // * true
+ // This word-iterator object is initialized successfully.
+ // * false
+ // An error occured while initializing this object.
+ void Initialize(const SpellcheckCharAttribute* attribute,
+ const char16* word,
+ size_t length,
+ bool allow_contraction);
+
+ // Retrieves a word (or a contraction).
+ // Parameters
+ // * word_string [out] (string16*)
+ // Represents a word (or a contraction) to be checked its spelling.
+ // This |word_string| has been already normalized to its canonical form
+ // (i.e. decomposed ligatures, replaced full-width latin characters to
+ // its ASCII alternatives, etc.) so that a SpellChecker object can check
+ // its spelling without any additional operations.
+ // On the other hand, a substring of the input string
+ // string16 str(&word[word_start], word_length);
+ // represents the non-normalized version of this extracted word.
+ // * word_start [out] (int*)
+ // Represents the offset of this word from the beginning of the input
+ // string, in UTF-16 characters.
+ // * word_length [out] (int*)
+ // Represents the length of an extracted word before normalization, in
+ // UTF-16 characters.
+ // When the input string contains ligatures, this value may not be equal
+ // to the length of the |word_string|.
+ // Return values
+ // * true
+ // Found a word (or a contraction) to be checked its spelling.
+ // * false
+ // Not found any more words or contractions to be checked their spellings.
+ bool GetNextWord(string16* word_string,
+ int* word_start,
+ int* word_length);
+
+ private:
+ // Retrieves a segment consisting of word characters (and contraction
+ // characters if the |allow_contraction| value is true).
+ void GetSegment(int* segment_start,
+ int* segment_end);
+
+ // Discards non-word characters at the beginning and the end of the given
+ // segment.
+ void TrimSegment(int segment_start,
+ int segment_end,
+ int* word_start,
+ int* word_length) const;
+
+ // Normalizes the given segment of the |word_| variable and write its
+ // canonical form to the |output_string|.
+ bool Normalize(int input_start,
+ int input_length,
+ string16* output_string) const;
+
+ private:
+ // The pointer to the input string from which we are extracting words.
+ const char16* word_;
+
+ // The length of the original string.
+ int length_;
+
+ // The current position in the original string.
+ int position_;
+
+ // The flag to control whether or not this object should extract possible
+ // contractions.
+ bool allow_contraction_;
+
+ // The character attributes used for filtering out non-word characters.
+ const SpellcheckCharAttribute* attribute_;
+
+ DISALLOW_COPY_AND_ASSIGN(SpellcheckWordIterator);
+};
+
+#endif // CHROME_RENDERER_SPELLCHECKER_SPELLCHECK_WORDITERATOR_H_
diff --git a/chrome/test/testing_profile.h b/chrome/test/testing_profile.h
index 34f1ef6..0d13268 100644
--- a/chrome/test/testing_profile.h
+++ b/chrome/test/testing_profile.h
@@ -178,6 +178,10 @@ class TestingProfile : public Profile {
virtual void ReinitializeSpellChecker() {}
virtual SpellChecker* GetSpellChecker() { return NULL; }
virtual void DeleteSpellChecker() {}
+#if defined(SPELLCHECKER_IN_RENDERER)
+ virtual SpellCheckHost* GetSpellCheckHost() { return NULL; }
+ virtual void ReinitializeSpellCheckHost(bool force) { }
+#endif
virtual WebKitContext* GetWebKitContext() { return NULL; }
virtual WebKitContext* GetOffTheRecordWebKitContext() { return NULL; }
virtual void MarkAsCleanShutdown() {}