Home | History | Annotate | Download | only in browser
      1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "chrome/browser/spellcheck_host_impl.h"
      6 
      7 #include <set>
      8 
      9 #include "base/file_util.h"
     10 #include "base/logging.h"
     11 #include "base/path_service.h"
     12 #include "base/string_split.h"
     13 #include "base/threading/thread_restrictions.h"
     14 #include "base/utf_string_conversions.h"
     15 #include "chrome/browser/spellcheck_host_observer.h"
     16 #include "chrome/browser/spellchecker_platform_engine.h"
     17 #include "chrome/common/chrome_constants.h"
     18 #include "chrome/common/chrome_paths.h"
     19 #include "chrome/common/spellcheck_common.h"
     20 #include "content/common/notification_service.h"
     21 #include "googleurl/src/gurl.h"
     22 #include "net/url_request/url_request_context_getter.h"
     23 #include "third_party/hunspell/google/bdict.h"
     24 #include "ui/base/l10n/l10n_util.h"
     25 #if defined(OS_MACOSX)
     26 #include "base/metrics/histogram.h"
     27 #endif
     28 
     29 namespace {
     30 
     31 FilePath GetFirstChoiceFilePath(const std::string& language) {
     32   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
     33 
     34   FilePath dict_dir;
     35   PathService::Get(chrome::DIR_APP_DICTIONARIES, &dict_dir);
     36   return SpellCheckCommon::GetVersionedFileName(language, dict_dir);
     37 }
     38 
     39 #if defined(OS_MACOSX)
     40 // Collect metrics on how often Hunspell is used on OS X vs the native
     41 // spellchecker.
     42 void RecordSpellCheckStats(bool native_spellchecker_used,
     43                            const std::string& language) {
     44   static std::set<std::string> languages_seen;
     45 
     46   // Only count a language code once for each session..
     47   if (languages_seen.find(language) != languages_seen.end()) {
     48     return;
     49   }
     50   languages_seen.insert(language);
     51 
     52   enum {
     53     SPELLCHECK_OSX_NATIVE_SPELLCHECKER_USED = 0,
     54     SPELLCHECK_HUNSPELL_USED = 1
     55   };
     56 
     57   bool engine_used = native_spellchecker_used ?
     58                          SPELLCHECK_OSX_NATIVE_SPELLCHECKER_USED :
     59                          SPELLCHECK_HUNSPELL_USED;
     60 
     61   UMA_HISTOGRAM_COUNTS("SpellCheck.OSXEngineUsed", engine_used);
     62 }
     63 #endif
     64 
     65 #if defined(OS_WIN)
     66 FilePath GetFallbackFilePath(const FilePath& first_choice) {
     67   FilePath dict_dir;
     68   PathService::Get(chrome::DIR_USER_DATA, &dict_dir);
     69   return dict_dir.Append(first_choice.BaseName());
     70 }
     71 #endif
     72 
     73 }  // namespace
     74 
     75 // Constructed on UI thread.
     76 SpellCheckHostImpl::SpellCheckHostImpl(
     77     SpellCheckHostObserver* observer,
     78     const std::string& language,
     79     net::URLRequestContextGetter* request_context_getter)
     80     : observer_(observer),
     81       language_(language),
     82       file_(base::kInvalidPlatformFileValue),
     83       tried_to_download_(false),
     84       use_platform_spellchecker_(false),
     85       request_context_getter_(request_context_getter) {
     86   DCHECK(observer_);
     87   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
     88 
     89   FilePath personal_file_directory;
     90   PathService::Get(chrome::DIR_USER_DATA, &personal_file_directory);
     91   custom_dictionary_file_ =
     92       personal_file_directory.Append(chrome::kCustomDictionaryFileName);
     93 }
     94 
     95 SpellCheckHostImpl::~SpellCheckHostImpl() {
     96   if (file_ != base::kInvalidPlatformFileValue)
     97     base::ClosePlatformFile(file_);
     98 }
     99 
    100 void SpellCheckHostImpl::Initialize() {
    101   if (SpellCheckerPlatform::SpellCheckerAvailable() &&
    102       SpellCheckerPlatform::PlatformSupportsLanguage(language_)) {
    103 #if defined(OS_MACOSX)
    104     RecordSpellCheckStats(true, language_);
    105 #endif
    106     use_platform_spellchecker_ = true;
    107     SpellCheckerPlatform::SetLanguage(language_);
    108     MessageLoop::current()->PostTask(FROM_HERE,
    109         NewRunnableMethod(this,
    110             &SpellCheckHostImpl::InformObserverOfInitialization));
    111     return;
    112   }
    113 
    114 #if defined(OS_MACOSX)
    115   RecordSpellCheckStats(false, language_);
    116 #endif
    117 
    118   BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
    119       NewRunnableMethod(this,
    120                         &SpellCheckHostImpl::InitializeDictionaryLocation));
    121 }
    122 
    123 void SpellCheckHostImpl::UnsetObserver() {
    124   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    125 
    126   observer_ = NULL;
    127   request_context_getter_ = NULL;
    128   fetcher_.reset();
    129 }
    130 
    131 void SpellCheckHostImpl::AddWord(const std::string& word) {
    132   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    133 
    134   custom_words_.push_back(word);
    135   BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
    136       NewRunnableMethod(this,
    137           &SpellCheckHostImpl::WriteWordToCustomDictionary, word));
    138   NotificationService::current()->Notify(
    139       NotificationType::SPELLCHECK_WORD_ADDED,
    140       Source<SpellCheckHost>(this), NotificationService::NoDetails());
    141 }
    142 
    143 void SpellCheckHostImpl::InitializeDictionaryLocation() {
    144   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
    145 
    146   // Initialize the BDICT path. This initialization should be in the FILE thread
    147   // because it checks if there is a "Dictionaries" directory and create it.
    148   if (bdict_file_path_.empty())
    149     bdict_file_path_ = GetFirstChoiceFilePath(language_);
    150 
    151 #if defined(OS_WIN)
    152   // Check if the dictionary exists in the fallback location. If so, use it
    153   // rather than downloading anew.
    154   FilePath fallback = GetFallbackFilePath(bdict_file_path_);
    155   if (!file_util::PathExists(bdict_file_path_) &&
    156       file_util::PathExists(fallback)) {
    157     bdict_file_path_ = fallback;
    158   }
    159 #endif
    160 
    161   InitializeInternal();
    162 }
    163 
    164 void SpellCheckHostImpl::InitializeInternal() {
    165   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
    166 
    167   if (!observer_)
    168     return;
    169 
    170   file_ = base::CreatePlatformFile(
    171       bdict_file_path_,
    172       base::PLATFORM_FILE_READ | base::PLATFORM_FILE_OPEN,
    173       NULL, NULL);
    174 
    175   // File didn't exist. Download it.
    176   if (file_ == base::kInvalidPlatformFileValue && !tried_to_download_ &&
    177       request_context_getter_) {
    178     // We download from the ui thread because we need to know that
    179     // |request_context_getter_| is still valid before initiating the download.
    180     BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
    181         NewRunnableMethod(this, &SpellCheckHostImpl::DownloadDictionary));
    182     return;
    183   }
    184 
    185   request_context_getter_ = NULL;
    186 
    187   if (file_ != base::kInvalidPlatformFileValue) {
    188     // Load custom dictionary.
    189     std::string contents;
    190     file_util::ReadFileToString(custom_dictionary_file_, &contents);
    191     std::vector<std::string> list_of_words;
    192     base::SplitString(contents, '\n', &list_of_words);
    193     for (size_t i = 0; i < list_of_words.size(); ++i)
    194       custom_words_.push_back(list_of_words[i]);
    195   }
    196 
    197   BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
    198       NewRunnableMethod(this,
    199           &SpellCheckHostImpl::InformObserverOfInitialization));
    200 }
    201 
    202 void SpellCheckHostImpl::InitializeOnFileThread() {
    203   DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::FILE));
    204 
    205   BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
    206       NewRunnableMethod(this, &SpellCheckHostImpl::Initialize));
    207 }
    208 
    209 void SpellCheckHostImpl::InformObserverOfInitialization() {
    210   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    211 
    212   if (observer_)
    213     observer_->SpellCheckHostInitialized();
    214 }
    215 
    216 void SpellCheckHostImpl::DownloadDictionary() {
    217   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    218 
    219   if (!request_context_getter_) {
    220     InitializeOnFileThread();
    221     return;
    222   }
    223 
    224   // Determine URL of file to download.
    225   static const char kDownloadServerUrl[] =
    226       "http://cache.pack.google.com/edgedl/chrome/dict/";
    227   std::string bdict_file = bdict_file_path_.BaseName().MaybeAsASCII();
    228   if (bdict_file.empty()) {
    229     NOTREACHED();
    230     return;
    231   }
    232   GURL url = GURL(std::string(kDownloadServerUrl) +
    233                   StringToLowerASCII(bdict_file));
    234   fetcher_.reset(new URLFetcher(url, URLFetcher::GET, this));
    235   fetcher_->set_request_context(request_context_getter_);
    236   tried_to_download_ = true;
    237   fetcher_->Start();
    238   request_context_getter_ = NULL;
    239 }
    240 
    241 void SpellCheckHostImpl::WriteWordToCustomDictionary(const std::string& word) {
    242   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
    243 
    244   // Stored in UTF-8.
    245   std::string word_to_add(word + "\n");
    246   FILE* f = file_util::OpenFile(custom_dictionary_file_, "a+");
    247   if (f)
    248     fputs(word_to_add.c_str(), f);
    249   file_util::CloseFile(f);
    250 }
    251 
    252 void SpellCheckHostImpl::OnURLFetchComplete(const URLFetcher* source,
    253                                             const GURL& url,
    254                                             const net::URLRequestStatus& status,
    255                                             int response_code,
    256                                             const ResponseCookies& cookies,
    257                                             const std::string& data) {
    258   DCHECK(source);
    259   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    260   fetcher_.reset();
    261 
    262   if ((response_code / 100) != 2) {
    263     // Initialize will not try to download the file a second time.
    264     LOG(ERROR) << "Failure to download dictionary.";
    265     InitializeOnFileThread();
    266     return;
    267   }
    268 
    269   // Basic sanity check on the dictionary.
    270   // There's the small chance that we might see a 200 status code for a body
    271   // that represents some form of failure.
    272   if (data.size() < 4 || data[0] != 'B' || data[1] != 'D' || data[2] != 'i' ||
    273       data[3] != 'c') {
    274     LOG(ERROR) << "Failure to download dictionary.";
    275     InitializeOnFileThread();
    276     return;
    277   }
    278 
    279   data_ = data;
    280   BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
    281       NewRunnableMethod(this, &SpellCheckHostImpl::SaveDictionaryData));
    282 }
    283 
    284 void SpellCheckHostImpl::SaveDictionaryData() {
    285   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
    286 
    287   // To prevent corrupted dictionary data from causing a renderer crash, scan
    288   // the dictionary data and verify it is sane before save it to a file.
    289   if (!hunspell::BDict::Verify(data_.data(), data_.size())) {
    290     LOG(ERROR) << "Failure to verify the downloaded dictionary.";
    291     BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
    292         NewRunnableMethod(this,
    293                           &SpellCheckHostImpl::InformObserverOfInitialization));
    294     return;
    295   }
    296 
    297   size_t bytes_written =
    298       file_util::WriteFile(bdict_file_path_, data_.data(), data_.length());
    299   if (bytes_written != data_.length()) {
    300     bool success = false;
    301 #if defined(OS_WIN)
    302     bdict_file_path_ = GetFallbackFilePath(bdict_file_path_);
    303     bytes_written =
    304         file_util::WriteFile(GetFallbackFilePath(bdict_file_path_),
    305                                                  data_.data(), data_.length());
    306     if (bytes_written == data_.length())
    307       success = true;
    308 #endif
    309     data_.clear();
    310 
    311     if (!success) {
    312       LOG(ERROR) << "Failure to save dictionary.";
    313       file_util::Delete(bdict_file_path_, false);
    314       // To avoid trying to load a partially saved dictionary, shortcut the
    315       // Initialize() call.
    316       BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
    317           NewRunnableMethod(this,
    318               &SpellCheckHostImpl::InformObserverOfInitialization));
    319       return;
    320     }
    321   }
    322 
    323   data_.clear();
    324   Initialize();
    325 }
    326 
    327 const base::PlatformFile& SpellCheckHostImpl::GetDictionaryFile() const {
    328   return file_;
    329 }
    330 
    331 const std::vector<std::string>& SpellCheckHostImpl::GetCustomWords() const {
    332   return custom_words_;
    333 }
    334 
    335 const std::string& SpellCheckHostImpl::GetLastAddedFile() const {
    336   return custom_words_.back();
    337 }
    338 
    339 const std::string& SpellCheckHostImpl::GetLanguage() const {
    340   return language_;
    341 }
    342 
    343 bool SpellCheckHostImpl::IsUsingPlatformChecker() const {
    344   return use_platform_spellchecker_;
    345 }
    346