summaryrefslogtreecommitdiffstats
path: root/chrome/browser/spellcheck_host.cc
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/browser/spellcheck_host.cc
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/browser/spellcheck_host.cc')
-rw-r--r--chrome/browser/spellcheck_host.cc278
1 files changed, 278 insertions, 0 deletions
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();
+}