diff options
author | estade@chromium.org <estade@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-11-06 03:05:46 +0000 |
---|---|---|
committer | estade@chromium.org <estade@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-11-06 03:05:46 +0000 |
commit | 85c55dcd717445cd3763b5c94f9902b4cdd194b0 (patch) | |
tree | 2deea721cfac202e3eb8556f66a4cf317a331288 /chrome/browser | |
parent | f1a8b962f0a6f1deb6c8c05a3f86d541e2ba61dd (diff) | |
download | chromium_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/browser')
-rw-r--r-- | chrome/browser/automation/automation_profile_impl.h | 8 | ||||
-rw-r--r-- | chrome/browser/profile.cc | 68 | ||||
-rw-r--r-- | chrome/browser/profile.h | 33 | ||||
-rw-r--r-- | chrome/browser/renderer_host/browser_render_process_host.cc | 84 | ||||
-rw-r--r-- | chrome/browser/renderer_host/browser_render_process_host.h | 19 | ||||
-rw-r--r-- | chrome/browser/spellcheck_host.cc | 278 | ||||
-rw-r--r-- | chrome/browser/spellcheck_host.h | 103 | ||||
-rw-r--r-- | chrome/browser/spellchecker.cc | 1 | ||||
-rw-r--r-- | chrome/browser/spellchecker_mac.mm | 2 | ||||
-rw-r--r-- | chrome/browser/tab_contents/render_view_context_menu.cc | 17 |
10 files changed, 608 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( |