Home | History | Annotate | Download | only in browser
      1 // Copyright 2014 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 "components/translate/core/browser/translate_manager.h"
      6 
      7 #include "base/bind.h"
      8 #include "base/command_line.h"
      9 #include "base/metrics/field_trial.h"
     10 #include "base/metrics/histogram.h"
     11 #include "base/prefs/pref_service.h"
     12 #include "base/strings/string_split.h"
     13 #include "base/strings/stringprintf.h"
     14 #include "base/time/time.h"
     15 #include "components/translate/core/browser/language_state.h"
     16 #include "components/translate/core/browser/page_translated_details.h"
     17 #include "components/translate/core/browser/translate_accept_languages.h"
     18 #include "components/translate/core/browser/translate_browser_metrics.h"
     19 #include "components/translate/core/browser/translate_client.h"
     20 #include "components/translate/core/browser/translate_download_manager.h"
     21 #include "components/translate/core/browser/translate_driver.h"
     22 #include "components/translate/core/browser/translate_error_details.h"
     23 #include "components/translate/core/browser/translate_language_list.h"
     24 #include "components/translate/core/browser/translate_prefs.h"
     25 #include "components/translate/core/browser/translate_script.h"
     26 #include "components/translate/core/browser/translate_url_util.h"
     27 #include "components/translate/core/common/language_detection_details.h"
     28 #include "components/translate/core/common/translate_constants.h"
     29 #include "components/translate/core/common/translate_pref_names.h"
     30 #include "components/translate/core/common/translate_switches.h"
     31 #include "net/base/url_util.h"
     32 #include "net/http/http_status_code.h"
     33 
     34 namespace translate {
     35 
     36 namespace {
     37 
     38 // Callbacks for translate errors.
     39 TranslateManager::TranslateErrorCallbackList* g_callback_list_ = NULL;
     40 
     41 const char kReportLanguageDetectionErrorURL[] =
     42     "https://translate.google.com/translate_error?client=cr&action=langidc";
     43 
     44 // Used in kReportLanguageDetectionErrorURL to specify the original page
     45 // language.
     46 const char kSourceLanguageQueryName[] = "sl";
     47 
     48 // Used in kReportLanguageDetectionErrorURL to specify the page URL.
     49 const char kUrlQueryName[] = "u";
     50 
     51 // Notifies |g_callback_list_| of translate errors.
     52 void NotifyTranslateError(const TranslateErrorDetails& details) {
     53   if (!g_callback_list_)
     54     return;
     55 
     56   g_callback_list_->Notify(details);
     57 }
     58 
     59 }  // namespace
     60 
     61 TranslateManager::~TranslateManager() {}
     62 
     63 // static
     64 scoped_ptr<TranslateManager::TranslateErrorCallbackList::Subscription>
     65 TranslateManager::RegisterTranslateErrorCallback(
     66     const TranslateManager::TranslateErrorCallback& callback) {
     67   if (!g_callback_list_)
     68     g_callback_list_ = new TranslateErrorCallbackList;
     69   return g_callback_list_->Add(callback);
     70 }
     71 
     72 TranslateManager::TranslateManager(
     73     TranslateClient* translate_client,
     74     const std::string& accept_languages_pref_name)
     75     : page_seq_no_(0),
     76       accept_languages_pref_name_(accept_languages_pref_name),
     77       translate_client_(translate_client),
     78       translate_driver_(translate_client_->GetTranslateDriver()),
     79       language_state_(translate_driver_),
     80       weak_method_factory_(this) {
     81 }
     82 
     83 base::WeakPtr<TranslateManager> TranslateManager::GetWeakPtr() {
     84   return weak_method_factory_.GetWeakPtr();
     85 }
     86 
     87 void TranslateManager::InitiateTranslation(const std::string& page_lang) {
     88   // Short-circuit out if not in a state where initiating translation makes
     89   // sense (this method may be called muhtiple times for a given page).
     90   if (!language_state_.page_needs_translation() ||
     91       language_state_.translation_pending() ||
     92       language_state_.translation_declined() ||
     93       language_state_.IsPageTranslated()) {
     94     return;
     95   }
     96 
     97   PrefService* prefs = translate_client_->GetPrefs();
     98   if (!prefs->GetBoolean(prefs::kEnableTranslate)) {
     99     TranslateBrowserMetrics::ReportInitiationStatus(
    100         TranslateBrowserMetrics::INITIATION_STATUS_DISABLED_BY_PREFS);
    101     const std::string& locale =
    102         TranslateDownloadManager::GetInstance()->application_locale();
    103     TranslateBrowserMetrics::ReportLocalesOnDisabledByPrefs(locale);
    104     return;
    105   }
    106 
    107   // Allow disabling of translate from the command line to assist with
    108   // automated browser testing.
    109   if (CommandLine::ForCurrentProcess()->HasSwitch(
    110           translate::switches::kDisableTranslate)) {
    111     TranslateBrowserMetrics::ReportInitiationStatus(
    112         TranslateBrowserMetrics::INITIATION_STATUS_DISABLED_BY_SWITCH);
    113     return;
    114   }
    115 
    116   // MHTML pages currently cannot be translated.
    117   // See bug: 217945.
    118   if (translate_driver_->GetContentsMimeType() == "multipart/related") {
    119     TranslateBrowserMetrics::ReportInitiationStatus(
    120         TranslateBrowserMetrics::INITIATION_STATUS_MIME_TYPE_IS_NOT_SUPPORTED);
    121     return;
    122   }
    123 
    124   // Don't translate any Chrome specific page, e.g., New Tab Page, Download,
    125   // History, and so on.
    126   const GURL& page_url = translate_driver_->GetVisibleURL();
    127   if (!translate_client_->IsTranslatableURL(page_url)) {
    128     TranslateBrowserMetrics::ReportInitiationStatus(
    129         TranslateBrowserMetrics::INITIATION_STATUS_URL_IS_NOT_SUPPORTED);
    130     return;
    131   }
    132 
    133   // Get the accepted languages list.
    134   std::vector<std::string> accept_languages_list;
    135   base::SplitString(prefs->GetString(accept_languages_pref_name_.c_str()), ',',
    136                     &accept_languages_list);
    137 
    138   std::string target_lang = GetTargetLanguage(accept_languages_list);
    139   std::string language_code =
    140       TranslateDownloadManager::GetLanguageCode(page_lang);
    141 
    142   // Don't translate similar languages (ex: en-US to en).
    143   if (language_code == target_lang) {
    144     TranslateBrowserMetrics::ReportInitiationStatus(
    145         TranslateBrowserMetrics::INITIATION_STATUS_SIMILAR_LANGUAGES);
    146     return;
    147   }
    148 
    149   // Nothing to do if either the language Chrome is in or the language of the
    150   // page is not supported by the translation server.
    151   if (target_lang.empty() ||
    152       !TranslateDownloadManager::IsSupportedLanguage(language_code)) {
    153     TranslateBrowserMetrics::ReportInitiationStatus(
    154         TranslateBrowserMetrics::INITIATION_STATUS_LANGUAGE_IS_NOT_SUPPORTED);
    155     TranslateBrowserMetrics::ReportUnsupportedLanguageAtInitiation(
    156         language_code);
    157     return;
    158   }
    159 
    160   scoped_ptr<TranslatePrefs> translate_prefs(
    161       translate_client_->GetTranslatePrefs());
    162 
    163   TranslateAcceptLanguages* accept_languages =
    164       translate_client_->GetTranslateAcceptLanguages();
    165   // Don't translate any user black-listed languages.
    166   if (!translate_prefs->CanTranslateLanguage(accept_languages,
    167                                              language_code)) {
    168     TranslateBrowserMetrics::ReportInitiationStatus(
    169         TranslateBrowserMetrics::INITIATION_STATUS_DISABLED_BY_CONFIG);
    170     return;
    171   }
    172 
    173   // Don't translate any user black-listed URLs.
    174   if (translate_prefs->IsSiteBlacklisted(page_url.HostNoBrackets())) {
    175     TranslateBrowserMetrics::ReportInitiationStatus(
    176         TranslateBrowserMetrics::INITIATION_STATUS_DISABLED_BY_CONFIG);
    177     return;
    178   }
    179 
    180   // If the user has previously selected "always translate" for this language we
    181   // automatically translate.  Note that in incognito mode we disable that
    182   // feature; the user will get an infobar, so they can control whether the
    183   // page's text is sent to the translate server.
    184   if (!translate_driver_->IsOffTheRecord()) {
    185     scoped_ptr<TranslatePrefs> translate_prefs =
    186         translate_client_->GetTranslatePrefs();
    187     std::string auto_target_lang =
    188         GetAutoTargetLanguage(language_code, translate_prefs.get());
    189     if (!auto_target_lang.empty()) {
    190       TranslateBrowserMetrics::ReportInitiationStatus(
    191           TranslateBrowserMetrics::INITIATION_STATUS_AUTO_BY_CONFIG);
    192       TranslatePage(language_code, auto_target_lang, false);
    193       return;
    194     }
    195   }
    196 
    197   std::string auto_translate_to = language_state_.AutoTranslateTo();
    198   if (!auto_translate_to.empty()) {
    199     // This page was navigated through a click from a translated page.
    200     TranslateBrowserMetrics::ReportInitiationStatus(
    201         TranslateBrowserMetrics::INITIATION_STATUS_AUTO_BY_LINK);
    202     TranslatePage(language_code, auto_translate_to, false);
    203     return;
    204   }
    205 
    206   TranslateBrowserMetrics::ReportInitiationStatus(
    207       TranslateBrowserMetrics::INITIATION_STATUS_SHOW_INFOBAR);
    208 
    209   // Prompts the user if he/she wants the page translated.
    210   translate_client_->ShowTranslateUI(translate::TRANSLATE_STEP_BEFORE_TRANSLATE,
    211                                      language_code,
    212                                      target_lang,
    213                                      TranslateErrors::NONE,
    214                                      false);
    215 }
    216 
    217 void TranslateManager::TranslatePage(const std::string& original_source_lang,
    218                                      const std::string& target_lang,
    219                                      bool triggered_from_menu) {
    220   if (!translate_driver_->HasCurrentPage()) {
    221     NOTREACHED();
    222     return;
    223   }
    224 
    225   // Translation can be kicked by context menu against unsupported languages.
    226   // Unsupported language strings should be replaced with
    227   // kUnknownLanguageCode in order to send a translation request with enabling
    228   // server side auto language detection.
    229   std::string source_lang(original_source_lang);
    230   if (!TranslateDownloadManager::IsSupportedLanguage(source_lang))
    231     source_lang = std::string(translate::kUnknownLanguageCode);
    232 
    233   translate_client_->ShowTranslateUI(translate::TRANSLATE_STEP_TRANSLATING,
    234                                      source_lang,
    235                                      target_lang,
    236                                      TranslateErrors::NONE,
    237                                      triggered_from_menu);
    238 
    239   TranslateScript* script = TranslateDownloadManager::GetInstance()->script();
    240   DCHECK(script != NULL);
    241 
    242   const std::string& script_data = script->data();
    243   if (!script_data.empty()) {
    244     DoTranslatePage(script_data, source_lang, target_lang);
    245     return;
    246   }
    247 
    248   // The script is not available yet.  Queue that request and query for the
    249   // script.  Once it is downloaded we'll do the translate.
    250   TranslateScript::RequestCallback callback = base::Bind(
    251       &TranslateManager::OnTranslateScriptFetchComplete, GetWeakPtr(),
    252       source_lang, target_lang);
    253 
    254   script->Request(callback);
    255 }
    256 
    257 void TranslateManager::RevertTranslation() {
    258   translate_driver_->RevertTranslation(page_seq_no_);
    259   language_state_.SetCurrentLanguage(language_state_.original_language());
    260 }
    261 
    262 void TranslateManager::ReportLanguageDetectionError() {
    263   TranslateBrowserMetrics::ReportLanguageDetectionError();
    264 
    265   GURL report_error_url = GURL(kReportLanguageDetectionErrorURL);
    266 
    267   report_error_url =
    268       net::AppendQueryParameter(report_error_url,
    269                                 kUrlQueryName,
    270                                 translate_driver_->GetActiveURL().spec());
    271 
    272   report_error_url =
    273       net::AppendQueryParameter(report_error_url,
    274                                 kSourceLanguageQueryName,
    275                                 language_state_.original_language());
    276 
    277   report_error_url = translate::AddHostLocaleToUrl(report_error_url);
    278   report_error_url = translate::AddApiKeyToUrl(report_error_url);
    279 
    280   translate_client_->ShowReportLanguageDetectionErrorUI(report_error_url);
    281 }
    282 
    283 void TranslateManager::DoTranslatePage(const std::string& translate_script,
    284                                        const std::string& source_lang,
    285                                        const std::string& target_lang) {
    286   language_state_.set_translation_pending(true);
    287   translate_driver_->TranslatePage(
    288       page_seq_no_, translate_script, source_lang, target_lang);
    289 }
    290 
    291 void TranslateManager::PageTranslated(const std::string& source_lang,
    292                                       const std::string& target_lang,
    293                                       TranslateErrors::Type error_type) {
    294   language_state_.SetCurrentLanguage(target_lang);
    295   language_state_.set_translation_pending(false);
    296 
    297   if ((error_type == TranslateErrors::NONE) &&
    298       source_lang != translate::kUnknownLanguageCode &&
    299       !TranslateDownloadManager::IsSupportedLanguage(source_lang)) {
    300     error_type = TranslateErrors::UNSUPPORTED_LANGUAGE;
    301   }
    302 
    303   translate_client_->ShowTranslateUI(translate::TRANSLATE_STEP_AFTER_TRANSLATE,
    304                                      source_lang,
    305                                      target_lang,
    306                                      error_type,
    307                                      false);
    308 
    309   if (error_type != TranslateErrors::NONE &&
    310       !translate_driver_->IsOffTheRecord()) {
    311     TranslateErrorDetails error_details;
    312     error_details.time = base::Time::Now();
    313     error_details.url = translate_driver_->GetLastCommittedURL();
    314     error_details.error = error_type;
    315     NotifyTranslateError(error_details);
    316   }
    317 }
    318 
    319 void TranslateManager::OnTranslateScriptFetchComplete(
    320     const std::string& source_lang,
    321     const std::string& target_lang,
    322     bool success,
    323     const std::string& data) {
    324   if (!translate_driver_->HasCurrentPage())
    325     return;
    326 
    327   if (success) {
    328     // Translate the page.
    329     TranslateScript* translate_script =
    330         TranslateDownloadManager::GetInstance()->script();
    331     DCHECK(translate_script);
    332     DoTranslatePage(translate_script->data(), source_lang, target_lang);
    333   } else {
    334     translate_client_->ShowTranslateUI(
    335         translate::TRANSLATE_STEP_TRANSLATE_ERROR,
    336         source_lang,
    337         target_lang,
    338         TranslateErrors::NETWORK,
    339         false);
    340     if (!translate_driver_->IsOffTheRecord()) {
    341       TranslateErrorDetails error_details;
    342       error_details.time = base::Time::Now();
    343       error_details.url = translate_driver_->GetActiveURL();
    344       error_details.error = TranslateErrors::NETWORK;
    345       NotifyTranslateError(error_details);
    346     }
    347   }
    348 }
    349 
    350 // static
    351 std::string TranslateManager::GetTargetLanguage(
    352     const std::vector<std::string>& accept_languages_list) {
    353   std::string ui_lang = TranslatePrefs::ConvertLangCodeForTranslation(
    354       TranslateDownloadManager::GetLanguageCode(
    355           TranslateDownloadManager::GetInstance()->application_locale()));
    356 
    357   if (TranslateDownloadManager::IsSupportedLanguage(ui_lang))
    358     return ui_lang;
    359 
    360   // Will translate to the first supported language on the Accepted Language
    361   // list or not at all if no such candidate exists
    362   std::vector<std::string>::const_iterator iter;
    363   for (iter = accept_languages_list.begin();
    364        iter != accept_languages_list.end(); ++iter) {
    365     std::string lang_code = TranslateDownloadManager::GetLanguageCode(*iter);
    366     if (TranslateDownloadManager::IsSupportedLanguage(lang_code))
    367       return lang_code;
    368   }
    369   return std::string();
    370 }
    371 
    372 // static
    373 std::string TranslateManager::GetAutoTargetLanguage(
    374     const std::string& original_language,
    375     TranslatePrefs* translate_prefs) {
    376   std::string auto_target_lang;
    377   if (translate_prefs->ShouldAutoTranslate(original_language,
    378                                            &auto_target_lang)) {
    379     // We need to confirm that the saved target language is still supported.
    380     // Also, GetLanguageCode will take care of removing country code if any.
    381     auto_target_lang =
    382         TranslateDownloadManager::GetLanguageCode(auto_target_lang);
    383     if (TranslateDownloadManager::IsSupportedLanguage(auto_target_lang))
    384       return auto_target_lang;
    385   }
    386   return std::string();
    387 }
    388 
    389 LanguageState& TranslateManager::GetLanguageState() {
    390   return language_state_;
    391 }
    392 
    393 }  // namespace translate
    394