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 {
     35 
     36 // Callbacks for translate errors.
     37 TranslateManager::TranslateErrorCallbackList* g_callback_list_ = NULL;
     38 
     39 const char kReportLanguageDetectionErrorURL[] =
     40     "https://translate.google.com/translate_error?client=cr&action=langidc";
     41 
     42 // Used in kReportLanguageDetectionErrorURL to specify the original page
     43 // language.
     44 const char kSourceLanguageQueryName[] = "sl";
     45 
     46 // Used in kReportLanguageDetectionErrorURL to specify the page URL.
     47 const char kUrlQueryName[] = "u";
     48 
     49 // Notifies |g_callback_list_| of translate errors.
     50 void NotifyTranslateError(const TranslateErrorDetails& details) {
     51   if (!g_callback_list_)
     52     return;
     53 
     54   g_callback_list_->Notify(details);
     55 }
     56 
     57 }  // namespace
     58 
     59 TranslateManager::~TranslateManager() {}
     60 
     61 // static
     62 scoped_ptr<TranslateManager::TranslateErrorCallbackList::Subscription>
     63 TranslateManager::RegisterTranslateErrorCallback(
     64     const TranslateManager::TranslateErrorCallback& callback) {
     65   if (!g_callback_list_)
     66     g_callback_list_ = new TranslateErrorCallbackList;
     67   return g_callback_list_->Add(callback);
     68 }
     69 
     70 TranslateManager::TranslateManager(
     71     TranslateClient* translate_client,
     72     const std::string& accept_languages_pref_name)
     73     : accept_languages_pref_name_(accept_languages_pref_name),
     74       translate_client_(translate_client),
     75       translate_driver_(translate_client_->GetTranslateDriver()),
     76       language_state_(translate_driver_),
     77       weak_method_factory_(this) {
     78 }
     79 
     80 base::WeakPtr<TranslateManager> TranslateManager::GetWeakPtr() {
     81   return weak_method_factory_.GetWeakPtr();
     82 }
     83 
     84 void TranslateManager::InitiateTranslation(const std::string& page_lang) {
     85   // Short-circuit out if not in a state where initiating translation makes
     86   // sense (this method may be called muhtiple times for a given page).
     87   if (!language_state_.page_needs_translation() ||
     88       language_state_.translation_pending() ||
     89       language_state_.translation_declined() ||
     90       language_state_.IsPageTranslated()) {
     91     return;
     92   }
     93 
     94   PrefService* prefs = translate_client_->GetPrefs();
     95   if (!prefs->GetBoolean(prefs::kEnableTranslate)) {
     96     TranslateBrowserMetrics::ReportInitiationStatus(
     97         TranslateBrowserMetrics::INITIATION_STATUS_DISABLED_BY_PREFS);
     98     const std::string& locale =
     99         TranslateDownloadManager::GetInstance()->application_locale();
    100     TranslateBrowserMetrics::ReportLocalesOnDisabledByPrefs(locale);
    101     return;
    102   }
    103 
    104   // Allow disabling of translate from the command line to assist with
    105   // automated browser testing.
    106   if (CommandLine::ForCurrentProcess()->HasSwitch(
    107           translate::switches::kDisableTranslate)) {
    108     TranslateBrowserMetrics::ReportInitiationStatus(
    109         TranslateBrowserMetrics::INITIATION_STATUS_DISABLED_BY_SWITCH);
    110     return;
    111   }
    112 
    113   // MHTML pages currently cannot be translated.
    114   // See bug: 217945.
    115   if (translate_driver_->GetContentsMimeType() == "multipart/related") {
    116     TranslateBrowserMetrics::ReportInitiationStatus(
    117         TranslateBrowserMetrics::INITIATION_STATUS_MIME_TYPE_IS_NOT_SUPPORTED);
    118     return;
    119   }
    120 
    121   // Don't translate any Chrome specific page, e.g., New Tab Page, Download,
    122   // History, and so on.
    123   const GURL& page_url = translate_driver_->GetVisibleURL();
    124   if (!translate_client_->IsTranslatableURL(page_url)) {
    125     TranslateBrowserMetrics::ReportInitiationStatus(
    126         TranslateBrowserMetrics::INITIATION_STATUS_URL_IS_NOT_SUPPORTED);
    127     return;
    128   }
    129 
    130   // Get the accepted languages list.
    131   std::vector<std::string> accept_languages_list;
    132   base::SplitString(prefs->GetString(accept_languages_pref_name_.c_str()), ',',
    133                     &accept_languages_list);
    134 
    135   std::string target_lang = GetTargetLanguage(accept_languages_list);
    136   std::string language_code =
    137       TranslateDownloadManager::GetLanguageCode(page_lang);
    138 
    139   // Don't translate similar languages (ex: en-US to en).
    140   if (language_code == target_lang) {
    141     TranslateBrowserMetrics::ReportInitiationStatus(
    142         TranslateBrowserMetrics::INITIATION_STATUS_SIMILAR_LANGUAGES);
    143     return;
    144   }
    145 
    146   // Nothing to do if either the language Chrome is in or the language of the
    147   // page is not supported by the translation server.
    148   if (target_lang.empty() ||
    149       !TranslateDownloadManager::IsSupportedLanguage(language_code)) {
    150     TranslateBrowserMetrics::ReportInitiationStatus(
    151         TranslateBrowserMetrics::INITIATION_STATUS_LANGUAGE_IS_NOT_SUPPORTED);
    152     TranslateBrowserMetrics::ReportUnsupportedLanguageAtInitiation(
    153         language_code);
    154     return;
    155   }
    156 
    157   scoped_ptr<TranslatePrefs> translate_prefs(
    158       translate_client_->GetTranslatePrefs());
    159 
    160   TranslateAcceptLanguages* accept_languages =
    161       translate_client_->GetTranslateAcceptLanguages();
    162   // Don't translate any user black-listed languages.
    163   if (!translate_prefs->CanTranslateLanguage(accept_languages,
    164                                              language_code)) {
    165     TranslateBrowserMetrics::ReportInitiationStatus(
    166         TranslateBrowserMetrics::INITIATION_STATUS_DISABLED_BY_CONFIG);
    167     return;
    168   }
    169 
    170   // Don't translate any user black-listed URLs.
    171   if (translate_prefs->IsSiteBlacklisted(page_url.HostNoBrackets())) {
    172     TranslateBrowserMetrics::ReportInitiationStatus(
    173         TranslateBrowserMetrics::INITIATION_STATUS_DISABLED_BY_CONFIG);
    174     return;
    175   }
    176 
    177   // If the user has previously selected "always translate" for this language we
    178   // automatically translate.  Note that in incognito mode we disable that
    179   // feature; the user will get an infobar, so they can control whether the
    180   // page's text is sent to the translate server.
    181   if (!translate_driver_->IsOffTheRecord()) {
    182     scoped_ptr<TranslatePrefs> translate_prefs =
    183         translate_client_->GetTranslatePrefs();
    184     std::string auto_target_lang =
    185         GetAutoTargetLanguage(language_code, translate_prefs.get());
    186     if (!auto_target_lang.empty()) {
    187       TranslateBrowserMetrics::ReportInitiationStatus(
    188           TranslateBrowserMetrics::INITIATION_STATUS_AUTO_BY_CONFIG);
    189       TranslatePage(language_code, auto_target_lang, false);
    190       return;
    191     }
    192   }
    193 
    194   std::string auto_translate_to = language_state_.AutoTranslateTo();
    195   if (!auto_translate_to.empty()) {
    196     // This page was navigated through a click from a translated page.
    197     TranslateBrowserMetrics::ReportInitiationStatus(
    198         TranslateBrowserMetrics::INITIATION_STATUS_AUTO_BY_LINK);
    199     TranslatePage(language_code, auto_translate_to, false);
    200     return;
    201   }
    202 
    203   TranslateBrowserMetrics::ReportInitiationStatus(
    204       TranslateBrowserMetrics::INITIATION_STATUS_SHOW_INFOBAR);
    205 
    206   // Prompts the user if he/she wants the page translated.
    207   translate_client_->ShowTranslateUI(translate::TRANSLATE_STEP_BEFORE_TRANSLATE,
    208                                      language_code,
    209                                      target_lang,
    210                                      TranslateErrors::NONE,
    211                                      false);
    212 }
    213 
    214 void TranslateManager::TranslatePage(const std::string& original_source_lang,
    215                                      const std::string& target_lang,
    216                                      bool triggered_from_menu) {
    217   if (!translate_driver_->HasCurrentPage()) {
    218     NOTREACHED();
    219     return;
    220   }
    221 
    222   // Translation can be kicked by context menu against unsupported languages.
    223   // Unsupported language strings should be replaced with
    224   // kUnknownLanguageCode in order to send a translation request with enabling
    225   // server side auto language detection.
    226   std::string source_lang(original_source_lang);
    227   if (!TranslateDownloadManager::IsSupportedLanguage(source_lang))
    228     source_lang = std::string(translate::kUnknownLanguageCode);
    229 
    230   translate_client_->ShowTranslateUI(translate::TRANSLATE_STEP_TRANSLATING,
    231                                      source_lang,
    232                                      target_lang,
    233                                      TranslateErrors::NONE,
    234                                      triggered_from_menu);
    235 
    236   TranslateScript* script = TranslateDownloadManager::GetInstance()->script();
    237   DCHECK(script != NULL);
    238 
    239   const std::string& script_data = script->data();
    240   if (!script_data.empty()) {
    241     DoTranslatePage(script_data, source_lang, target_lang);
    242     return;
    243   }
    244 
    245   // The script is not available yet.  Queue that request and query for the
    246   // script.  Once it is downloaded we'll do the translate.
    247   TranslateScript::RequestCallback callback = base::Bind(
    248       &TranslateManager::OnTranslateScriptFetchComplete, GetWeakPtr(),
    249       translate_driver_->GetCurrentPageID(), source_lang, target_lang);
    250 
    251   script->Request(callback);
    252 }
    253 
    254 void TranslateManager::RevertTranslation() {
    255   translate_driver_->RevertTranslation();
    256   language_state_.SetCurrentLanguage(language_state_.original_language());
    257 }
    258 
    259 void TranslateManager::ReportLanguageDetectionError() {
    260   TranslateBrowserMetrics::ReportLanguageDetectionError();
    261 
    262   GURL report_error_url = GURL(kReportLanguageDetectionErrorURL);
    263 
    264   report_error_url =
    265       net::AppendQueryParameter(report_error_url,
    266                                 kUrlQueryName,
    267                                 translate_driver_->GetActiveURL().spec());
    268 
    269   report_error_url =
    270       net::AppendQueryParameter(report_error_url,
    271                                 kSourceLanguageQueryName,
    272                                 language_state_.original_language());
    273 
    274   report_error_url = TranslateURLUtil::AddHostLocaleToUrl(report_error_url);
    275   report_error_url = TranslateURLUtil::AddApiKeyToUrl(report_error_url);
    276 
    277   translate_client_->ShowReportLanguageDetectionErrorUI(report_error_url);
    278 }
    279 
    280 void TranslateManager::DoTranslatePage(const std::string& translate_script,
    281                                        const std::string& source_lang,
    282                                        const std::string& target_lang) {
    283   language_state_.set_translation_pending(true);
    284   translate_driver_->TranslatePage(translate_script, source_lang, target_lang);
    285 }
    286 
    287 void TranslateManager::PageTranslated(const std::string& source_lang,
    288                                       const std::string& target_lang,
    289                                       TranslateErrors::Type error_type) {
    290   language_state_.SetCurrentLanguage(target_lang);
    291   language_state_.set_translation_pending(false);
    292 
    293   if ((error_type == TranslateErrors::NONE) &&
    294       source_lang != translate::kUnknownLanguageCode &&
    295       !TranslateDownloadManager::IsSupportedLanguage(source_lang)) {
    296     error_type = TranslateErrors::UNSUPPORTED_LANGUAGE;
    297   }
    298 
    299   translate_client_->ShowTranslateUI(translate::TRANSLATE_STEP_AFTER_TRANSLATE,
    300                                      source_lang,
    301                                      target_lang,
    302                                      error_type,
    303                                      false);
    304 
    305   if (error_type != TranslateErrors::NONE &&
    306       !translate_driver_->IsOffTheRecord()) {
    307     TranslateErrorDetails error_details;
    308     error_details.time = base::Time::Now();
    309     error_details.url = translate_driver_->GetLastCommittedURL();
    310     error_details.error = error_type;
    311     NotifyTranslateError(error_details);
    312   }
    313 }
    314 
    315 void TranslateManager::OnTranslateScriptFetchComplete(
    316     int page_id,
    317     const std::string& source_lang,
    318     const std::string& target_lang,
    319     bool success,
    320     const std::string& data) {
    321   if (!translate_driver_->HasCurrentPage() ||
    322       translate_driver_->GetCurrentPageID() != page_id) {
    323     // We navigated away from the page the translation was triggered on.
    324     return;
    325   }
    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