Home | History | Annotate | Download | only in translate
      1 // Copyright (c) 2012 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/translate/translate_manager.h"
      6 
      7 #include "base/bind.h"
      8 #include "base/command_line.h"
      9 #include "base/memory/singleton.h"
     10 #include "base/metrics/field_trial.h"
     11 #include "base/metrics/histogram.h"
     12 #include "base/prefs/pref_service.h"
     13 #include "base/strings/string_split.h"
     14 #include "base/strings/stringprintf.h"
     15 #include "base/time/time.h"
     16 #include "chrome/browser/browser_process.h"
     17 #include "chrome/browser/chrome_notification_types.h"
     18 #include "chrome/browser/profiles/profile.h"
     19 #include "chrome/browser/tab_contents/language_state.h"
     20 #include "chrome/browser/tab_contents/tab_util.h"
     21 #include "chrome/browser/translate/page_translated_details.h"
     22 #include "chrome/browser/translate/translate_accept_languages.h"
     23 #include "chrome/browser/translate/translate_browser_metrics.h"
     24 #include "chrome/browser/translate/translate_error_details.h"
     25 #include "chrome/browser/translate/translate_event_details.h"
     26 #include "chrome/browser/translate/translate_infobar_delegate.h"
     27 #include "chrome/browser/translate/translate_language_list.h"
     28 #include "chrome/browser/translate/translate_prefs.h"
     29 #include "chrome/browser/translate/translate_script.h"
     30 #include "chrome/browser/translate/translate_tab_helper.h"
     31 #include "chrome/browser/translate/translate_url_util.h"
     32 #include "chrome/browser/ui/browser.h"
     33 #include "chrome/browser/ui/browser_finder.h"
     34 #include "chrome/browser/ui/browser_tabstrip.h"
     35 #include "chrome/browser/ui/browser_window.h"
     36 #include "chrome/browser/ui/tabs/tab_strip_model.h"
     37 #include "chrome/browser/ui/translate/translate_bubble_factory.h"
     38 #include "chrome/common/chrome_switches.h"
     39 #include "chrome/common/pref_names.h"
     40 #include "chrome/common/render_messages.h"
     41 #include "chrome/common/translate/language_detection_details.h"
     42 #include "chrome/common/url_constants.h"
     43 #include "components/translate/common/translate_constants.h"
     44 #include "content/public/browser/navigation_controller.h"
     45 #include "content/public/browser/navigation_details.h"
     46 #include "content/public/browser/navigation_entry.h"
     47 #include "content/public/browser/notification_details.h"
     48 #include "content/public/browser/notification_service.h"
     49 #include "content/public/browser/notification_source.h"
     50 #include "content/public/browser/notification_types.h"
     51 #include "content/public/browser/render_process_host.h"
     52 #include "content/public/browser/render_view_host.h"
     53 #include "content/public/browser/web_contents.h"
     54 #include "net/base/url_util.h"
     55 #include "net/http/http_status_code.h"
     56 
     57 #ifdef FILE_MANAGER_EXTENSION
     58 #include "chrome/browser/chromeos/file_manager/app_id.h"
     59 #include "extensions/common/constants.h"
     60 #endif
     61 
     62 using content::NavigationController;
     63 using content::NavigationEntry;
     64 using content::WebContents;
     65 
     66 namespace {
     67 
     68 const char kReportLanguageDetectionErrorURL[] =
     69     "https://translate.google.com/translate_error?client=cr&action=langidc";
     70 
     71 // Used in kReportLanguageDetectionErrorURL to specify the original page
     72 // language.
     73 const char kSourceLanguageQueryName[] = "sl";
     74 
     75 // Used in kReportLanguageDetectionErrorURL to specify the page URL.
     76 const char kUrlQueryName[] = "u";
     77 
     78 // The maximum number of attempts we'll do to see if the page has finshed
     79 // loading before giving up the translation
     80 const int kMaxTranslateLoadCheckAttempts = 20;
     81 
     82 // The field trial name to compare Translate infobar and bubble.
     83 const char kFieldTrialNameForUX[] = "TranslateInfobarVsBubble";
     84 
     85 }  // namespace
     86 
     87 TranslateManager::~TranslateManager() {
     88 }
     89 
     90 // static
     91 TranslateManager* TranslateManager::GetInstance() {
     92   return Singleton<TranslateManager>::get();
     93 }
     94 
     95 // static
     96 bool TranslateManager::IsTranslatableURL(const GURL& url) {
     97   // A URLs is translatable unless it is one of the following:
     98   // - empty (can happen for popups created with window.open(""))
     99   // - an internal URL (chrome:// and others)
    100   // - the devtools (which is considered UI)
    101   // - Chrome OS file manager extension
    102   // - an FTP page (as FTP pages tend to have long lists of filenames that may
    103   //   confuse the CLD)
    104   return !url.is_empty() &&
    105          !url.SchemeIs(chrome::kChromeUIScheme) &&
    106          !url.SchemeIs(chrome::kChromeDevToolsScheme) &&
    107 #ifdef FILE_MANAGER_EXTENSION
    108          !(url.SchemeIs(extensions::kExtensionScheme) &&
    109            url.DomainIs(file_manager::kFileManagerAppId)) &&
    110 #endif
    111          !url.SchemeIs(content::kFtpScheme);
    112 }
    113 
    114 // static
    115 void TranslateManager::GetSupportedLanguages(
    116     std::vector<std::string>* languages) {
    117   if (GetInstance()->language_list_.get()) {
    118     GetInstance()->language_list_->GetSupportedLanguages(languages);
    119     return;
    120   }
    121   NOTREACHED();
    122 }
    123 
    124 // static
    125 base::Time TranslateManager::GetSupportedLanguagesLastUpdated() {
    126   if (GetInstance()->language_list_.get()) {
    127     return GetInstance()->language_list_->last_updated();
    128   }
    129   NOTREACHED();
    130   return base::Time();
    131 }
    132 
    133 // static
    134 std::string TranslateManager::GetLanguageCode(
    135     const std::string& chrome_locale) {
    136   if (GetInstance()->language_list_.get())
    137     return GetInstance()->language_list_->GetLanguageCode(chrome_locale);
    138   NOTREACHED();
    139   return chrome_locale;
    140 }
    141 
    142 // static
    143 bool TranslateManager::IsSupportedLanguage(const std::string& language) {
    144   if (GetInstance()->language_list_.get())
    145     return GetInstance()->language_list_->IsSupportedLanguage(language);
    146   NOTREACHED();
    147   return false;
    148 }
    149 
    150 // static
    151 bool TranslateManager::IsAlphaLanguage(const std::string& language) {
    152   if (GetInstance()->language_list_.get())
    153     return GetInstance()->language_list_->IsAlphaLanguage(language);
    154   NOTREACHED();
    155   return false;
    156 }
    157 
    158 // static
    159 bool TranslateManager::IsAcceptLanguage(Profile* profile,
    160                                         const std::string& language) {
    161   if (GetInstance()->accept_languages_.get()) {
    162     return GetInstance()->accept_languages_->IsAcceptLanguage(
    163         profile, language);
    164   }
    165   NOTREACHED();
    166   return false;
    167 }
    168 
    169 void TranslateManager::SetTranslateScriptExpirationDelay(int delay_ms) {
    170   if (script_.get() == NULL) {
    171     NOTREACHED();
    172     return;
    173   }
    174   script_->set_expiration_delay(delay_ms);
    175 }
    176 
    177 void TranslateManager::Observe(int type,
    178                                const content::NotificationSource& source,
    179                                const content::NotificationDetails& details) {
    180   switch (type) {
    181     case content::NOTIFICATION_NAV_ENTRY_COMMITTED: {
    182       NavigationController* controller =
    183           content::Source<NavigationController>(source).ptr();
    184       content::LoadCommittedDetails* load_details =
    185           content::Details<content::LoadCommittedDetails>(details).ptr();
    186       NavigationEntry* entry = controller->GetActiveEntry();
    187       if (!entry) {
    188         NOTREACHED();
    189         return;
    190       }
    191 
    192       TranslateTabHelper* translate_tab_helper =
    193           TranslateTabHelper::FromWebContents(controller->GetWebContents());
    194       if (!translate_tab_helper)
    195         return;
    196 
    197       // If the navigation happened while offline don't show the translate
    198       // bar since there will be nothing to translate.
    199       if (load_details->http_status_code == 0 ||
    200           load_details->http_status_code == net::HTTP_INTERNAL_SERVER_ERROR) {
    201         return;
    202       }
    203 
    204       if (!load_details->is_main_frame &&
    205           translate_tab_helper->language_state().translation_declined()) {
    206         // Some sites (such as Google map) may trigger sub-frame navigations
    207         // when the user interacts with the page.  We don't want to show a new
    208         // infobar if the user already dismissed one in that case.
    209         return;
    210       }
    211       if (entry->GetTransitionType() != content::PAGE_TRANSITION_RELOAD &&
    212           load_details->type != content::NAVIGATION_TYPE_SAME_PAGE) {
    213         return;
    214       }
    215 
    216       // When doing a page reload, TAB_LANGUAGE_DETERMINED is not sent,
    217       // so the translation needs to be explicitly initiated, but only when the
    218       // page needs translation.
    219       if (!translate_tab_helper->language_state().page_needs_translation())
    220         return;
    221       // Note that we delay it as the TranslateManager gets this notification
    222       // before the WebContents and the WebContents processing might remove the
    223       // current infobars.  Since InitTranslation might add an infobar, it must
    224       // be done after that.
    225       base::MessageLoop::current()->PostTask(FROM_HERE,
    226           base::Bind(
    227               &TranslateManager::InitiateTranslationPosted,
    228               weak_method_factory_.GetWeakPtr(),
    229               controller->GetWebContents()->GetRenderProcessHost()->GetID(),
    230               controller->GetWebContents()->GetRenderViewHost()->GetRoutingID(),
    231               translate_tab_helper->language_state().original_language(), 0));
    232       break;
    233     }
    234     case chrome::NOTIFICATION_TAB_LANGUAGE_DETERMINED: {
    235       const LanguageDetectionDetails* lang_det_details =
    236           content::Details<const LanguageDetectionDetails>(details).ptr();
    237 
    238       WebContents* tab = content::Source<WebContents>(source).ptr();
    239       if (!tab->GetBrowserContext()->IsOffTheRecord())
    240         NotifyLanguageDetection(*lang_det_details);
    241 
    242       // We may get this notifications multiple times.  Make sure to translate
    243       // only once.
    244       TranslateTabHelper* translate_tab_helper =
    245           TranslateTabHelper::FromWebContents(tab);
    246       if (!translate_tab_helper)
    247         return;
    248 
    249       LanguageState& language_state = translate_tab_helper->language_state();
    250       if (language_state.page_needs_translation() &&
    251           !language_state.translation_pending() &&
    252           !language_state.translation_declined() &&
    253           !language_state.IsPageTranslated()) {
    254         std::string language = lang_det_details->adopted_language;
    255         InitiateTranslation(tab, language);
    256       }
    257       break;
    258     }
    259     case chrome::NOTIFICATION_PAGE_TRANSLATED: {
    260       // Only add translate infobar if it doesn't exist; if it already exists,
    261       // just update the state, the actual infobar would have received the same
    262       //  notification and update the visual display accordingly.
    263       WebContents* tab = content::Source<WebContents>(source).ptr();
    264       PageTranslatedDetails* page_translated_details =
    265           content::Details<PageTranslatedDetails>(details).ptr();
    266       PageTranslated(tab, page_translated_details);
    267       break;
    268     }
    269     default:
    270       NOTREACHED();
    271   }
    272 }
    273 
    274 void TranslateManager::AddObserver(Observer* obs) {
    275   observer_list_.AddObserver(obs);
    276 }
    277 
    278 void TranslateManager::RemoveObserver(Observer* obs) {
    279   observer_list_.RemoveObserver(obs);
    280 }
    281 
    282 void TranslateManager::NotifyTranslateEvent(
    283     const TranslateEventDetails& details) {
    284   FOR_EACH_OBSERVER(Observer, observer_list_, OnTranslateEvent(details));
    285 }
    286 
    287 void TranslateManager::NotifyLanguageDetection(
    288     const LanguageDetectionDetails& details) {
    289   FOR_EACH_OBSERVER(Observer, observer_list_, OnLanguageDetection(details));
    290 }
    291 
    292 void TranslateManager::NotifyTranslateError(
    293     const TranslateErrorDetails& details) {
    294   FOR_EACH_OBSERVER(Observer, observer_list_, OnTranslateError(details));
    295 }
    296 
    297 TranslateManager::TranslateManager()
    298   : max_reload_check_attempts_(kMaxTranslateLoadCheckAttempts),
    299     weak_method_factory_(this) {
    300   notification_registrar_.Add(this, content::NOTIFICATION_NAV_ENTRY_COMMITTED,
    301                               content::NotificationService::AllSources());
    302   notification_registrar_.Add(this,
    303                               chrome::NOTIFICATION_TAB_LANGUAGE_DETERMINED,
    304                               content::NotificationService::AllSources());
    305   notification_registrar_.Add(this, chrome::NOTIFICATION_PAGE_TRANSLATED,
    306                               content::NotificationService::AllSources());
    307   language_list_.reset(new TranslateLanguageList);
    308   accept_languages_.reset(new TranslateAcceptLanguages);
    309   script_.reset(new TranslateScript);
    310 }
    311 
    312 void TranslateManager::InitiateTranslation(WebContents* web_contents,
    313                                            const std::string& page_lang) {
    314   TranslateTabHelper* translate_tab_helper =
    315       TranslateTabHelper::FromWebContents(web_contents);
    316   if (!translate_tab_helper)
    317     return;
    318 
    319   Profile* profile =
    320       Profile::FromBrowserContext(web_contents->GetBrowserContext());
    321   Profile* original_profile = profile->GetOriginalProfile();
    322   PrefService* prefs = original_profile->GetPrefs();
    323   if (!prefs->GetBoolean(prefs::kEnableTranslate)) {
    324     TranslateBrowserMetrics::ReportInitiationStatus(
    325         TranslateBrowserMetrics::INITIATION_STATUS_DISABLED_BY_PREFS);
    326     const std::string& locale = g_browser_process->GetApplicationLocale();
    327     TranslateBrowserMetrics::ReportLocalesOnDisabledByPrefs(locale);
    328     return;
    329   }
    330 
    331   // Allow disabling of translate from the command line to assist with
    332   // automated browser testing.
    333   if (CommandLine::ForCurrentProcess()->HasSwitch(
    334       switches::kDisableTranslate)) {
    335     TranslateBrowserMetrics::ReportInitiationStatus(
    336         TranslateBrowserMetrics::INITIATION_STATUS_DISABLED_BY_SWITCH);
    337     return;
    338   }
    339 
    340   // MHTML pages currently cannot be translated.
    341   // See bug: 217945.
    342   if (web_contents->GetContentsMimeType() == "multipart/related") {
    343     TranslateBrowserMetrics::ReportInitiationStatus(
    344         TranslateBrowserMetrics::INITIATION_STATUS_MIME_TYPE_IS_NOT_SUPPORTED);
    345     return;
    346   }
    347 
    348   // Don't translate any Chrome specific page, e.g., New Tab Page, Download,
    349   // History, and so on.
    350   GURL page_url = web_contents->GetURL();
    351   if (!IsTranslatableURL(page_url)) {
    352     TranslateBrowserMetrics::ReportInitiationStatus(
    353         TranslateBrowserMetrics::INITIATION_STATUS_URL_IS_NOT_SUPPORTED);
    354     return;
    355   }
    356 
    357   std::string target_lang = GetTargetLanguage(prefs);
    358   std::string language_code = GetLanguageCode(page_lang);
    359 
    360   // Don't translate similar languages (ex: en-US to en).
    361   if (language_code == target_lang) {
    362     TranslateBrowserMetrics::ReportInitiationStatus(
    363         TranslateBrowserMetrics::INITIATION_STATUS_SIMILAR_LANGUAGES);
    364     return;
    365   }
    366 
    367   // Nothing to do if either the language Chrome is in or the language of the
    368   // page is not supported by the translation server.
    369   if (target_lang.empty() || !IsSupportedLanguage(language_code)) {
    370     TranslateBrowserMetrics::ReportInitiationStatus(
    371         TranslateBrowserMetrics::INITIATION_STATUS_LANGUAGE_IS_NOT_SUPPORTED);
    372     TranslateBrowserMetrics::ReportUnsupportedLanguageAtInitiation(
    373         language_code);
    374     return;
    375   }
    376 
    377   TranslatePrefs translate_prefs(prefs);
    378 
    379   // Don't translate any user black-listed languages.
    380   if (!TranslatePrefs::CanTranslateLanguage(profile, language_code)) {
    381     TranslateBrowserMetrics::ReportInitiationStatus(
    382         TranslateBrowserMetrics::INITIATION_STATUS_DISABLED_BY_CONFIG);
    383     return;
    384   }
    385 
    386   // Don't translate any user black-listed URLs.
    387   if (translate_prefs.IsSiteBlacklisted(page_url.HostNoBrackets())) {
    388     TranslateBrowserMetrics::ReportInitiationStatus(
    389         TranslateBrowserMetrics::INITIATION_STATUS_DISABLED_BY_CONFIG);
    390     return;
    391   }
    392 
    393   // If the user has previously selected "always translate" for this language we
    394   // automatically translate.  Note that in incognito mode we disable that
    395   // feature; the user will get an infobar, so they can control whether the
    396   // page's text is sent to the translate server.
    397   if (!web_contents->GetBrowserContext()->IsOffTheRecord()) {
    398     std::string auto_target_lang = GetAutoTargetLanguage(language_code, prefs);
    399     if (!auto_target_lang.empty()) {
    400       TranslateBrowserMetrics::ReportInitiationStatus(
    401           TranslateBrowserMetrics::INITIATION_STATUS_AUTO_BY_CONFIG);
    402       TranslatePage(web_contents, language_code, auto_target_lang);
    403       return;
    404     }
    405   }
    406 
    407   LanguageState& language_state = translate_tab_helper->language_state();
    408   std::string auto_translate_to = language_state.AutoTranslateTo();
    409   if (!auto_translate_to.empty()) {
    410     // This page was navigated through a click from a translated page.
    411     TranslateBrowserMetrics::ReportInitiationStatus(
    412         TranslateBrowserMetrics::INITIATION_STATUS_AUTO_BY_LINK);
    413     TranslatePage(web_contents, language_code, auto_translate_to);
    414     return;
    415   }
    416 
    417   TranslateBrowserMetrics::ReportInitiationStatus(
    418       TranslateBrowserMetrics::INITIATION_STATUS_SHOW_INFOBAR);
    419 
    420   if (IsTranslateBubbleEnabled()) {
    421     language_state.SetTranslateEnabled(true);
    422     if (language_state.HasLanguageChanged()) {
    423       ShowBubble(web_contents,
    424                  TranslateBubbleModel::VIEW_STATE_BEFORE_TRANSLATE,
    425                  TranslateErrors::NONE);
    426     }
    427   } else {
    428     // Prompts the user if he/she wants the page translated.
    429     TranslateInfoBarDelegate::Create(
    430         false, web_contents, TranslateInfoBarDelegate::BEFORE_TRANSLATE,
    431         language_code, target_lang, TranslateErrors::NONE, profile->GetPrefs(),
    432         ShortcutConfig());
    433   }
    434 }
    435 
    436 void TranslateManager::InitiateTranslationPosted(int process_id,
    437                                                  int render_id,
    438                                                  const std::string& page_lang,
    439                                                  int attempt) {
    440   // The tab might have been closed.
    441   WebContents* web_contents =
    442       tab_util::GetWebContentsByID(process_id, render_id);
    443   if (!web_contents)
    444     return;
    445 
    446   TranslateTabHelper* translate_tab_helper =
    447       TranslateTabHelper::FromWebContents(web_contents);
    448   if (translate_tab_helper->language_state().translation_pending())
    449     return;
    450 
    451   // During a reload we need web content to be available before the
    452   // translate script is executed. Otherwise we will run the translate script on
    453   // an empty DOM which will fail. Therefore we wait a bit to see if the page
    454   // has finished.
    455   if ((web_contents->IsLoading()) && attempt < kMaxTranslateLoadCheckAttempts) {
    456     int backoff = attempt * max_reload_check_attempts_;
    457     base::MessageLoop::current()->PostDelayedTask(
    458         FROM_HERE, base::Bind(&TranslateManager::InitiateTranslationPosted,
    459                               weak_method_factory_.GetWeakPtr(), process_id,
    460                               render_id, page_lang, ++attempt),
    461         base::TimeDelta::FromMilliseconds(backoff));
    462     return;
    463   }
    464 
    465   InitiateTranslation(web_contents, GetLanguageCode(page_lang));
    466 }
    467 
    468 void TranslateManager::TranslatePage(WebContents* web_contents,
    469                                      const std::string& original_source_lang,
    470                                      const std::string& target_lang) {
    471   NavigationEntry* entry = web_contents->GetController().GetActiveEntry();
    472   if (!entry) {
    473     NOTREACHED();
    474     return;
    475   }
    476 
    477   // Translation can be kicked by context menu against unsupported languages.
    478   // Unsupported language strings should be replaced with
    479   // kUnknownLanguageCode in order to send a translation request with enabling
    480   // server side auto language detection.
    481   std::string source_lang(original_source_lang);
    482   if (!IsSupportedLanguage(source_lang))
    483     source_lang = std::string(translate::kUnknownLanguageCode);
    484 
    485   if (IsTranslateBubbleEnabled()) {
    486     ShowBubble(web_contents, TranslateBubbleModel::VIEW_STATE_TRANSLATING,
    487                TranslateErrors::NONE);
    488   } else {
    489     Profile* profile =
    490         Profile::FromBrowserContext(web_contents->GetBrowserContext());
    491     TranslateInfoBarDelegate::Create(
    492         true, web_contents, TranslateInfoBarDelegate::TRANSLATING, source_lang,
    493         target_lang, TranslateErrors::NONE, profile->GetPrefs(),
    494         ShortcutConfig());
    495   }
    496 
    497   DCHECK(script_.get() != NULL);
    498 
    499   const std::string& translate_script = script_->data();
    500   if (!translate_script.empty()) {
    501     DoTranslatePage(web_contents, translate_script, source_lang, target_lang);
    502     return;
    503   }
    504 
    505   // The script is not available yet.  Queue that request and query for the
    506   // script.  Once it is downloaded we'll do the translate.
    507   content::RenderViewHost* rvh = web_contents->GetRenderViewHost();
    508   PendingRequest request;
    509   request.render_process_id = rvh->GetProcess()->GetID();
    510   request.render_view_id = rvh->GetRoutingID();
    511   request.page_id = entry->GetPageID();
    512   request.source_lang = source_lang;
    513   request.target_lang = target_lang;
    514   pending_requests_.push_back(request);
    515 
    516   if (script_->HasPendingRequest())
    517     return;
    518 
    519   script_->Request(
    520       base::Bind(&TranslateManager::OnTranslateScriptFetchComplete,
    521                  base::Unretained(this)));
    522 }
    523 
    524 void TranslateManager::RevertTranslation(WebContents* web_contents) {
    525   NavigationEntry* entry = web_contents->GetController().GetActiveEntry();
    526   if (!entry) {
    527     NOTREACHED();
    528     return;
    529   }
    530   web_contents->GetRenderViewHost()->Send(new ChromeViewMsg_RevertTranslation(
    531       web_contents->GetRenderViewHost()->GetRoutingID(), entry->GetPageID()));
    532 
    533   TranslateTabHelper* translate_tab_helper =
    534       TranslateTabHelper::FromWebContents(web_contents);
    535   translate_tab_helper->language_state().SetCurrentLanguage(
    536       translate_tab_helper->language_state().original_language());
    537 }
    538 
    539 void TranslateManager::ReportLanguageDetectionError(WebContents* web_contents) {
    540   TranslateBrowserMetrics::ReportLanguageDetectionError();
    541   // We'll open the URL in a new tab so that the user can tell us more.
    542   Browser* browser = chrome::FindBrowserWithWebContents(web_contents);
    543   if (!browser) {
    544     NOTREACHED();
    545     return;
    546   }
    547 
    548   GURL report_error_url = GURL(kReportLanguageDetectionErrorURL);
    549 
    550   GURL page_url = web_contents->GetController().GetActiveEntry()->GetURL();
    551   report_error_url = net::AppendQueryParameter(
    552       report_error_url,
    553       kUrlQueryName,
    554       page_url.spec());
    555 
    556   TranslateTabHelper* translate_tab_helper =
    557       TranslateTabHelper::FromWebContents(web_contents);
    558   report_error_url = net::AppendQueryParameter(
    559       report_error_url,
    560       kSourceLanguageQueryName,
    561       translate_tab_helper->language_state().original_language());
    562 
    563   report_error_url = TranslateURLUtil::AddHostLocaleToUrl(report_error_url);
    564   report_error_url = TranslateURLUtil::AddApiKeyToUrl(report_error_url);
    565 
    566   chrome::AddSelectedTabWithURL(browser, report_error_url,
    567                                 content::PAGE_TRANSITION_AUTO_BOOKMARK);
    568 }
    569 
    570 void TranslateManager::ClearTranslateScript() {
    571   if (script_.get() == NULL) {
    572     NOTREACHED();
    573     return;
    574   }
    575   script_->Clear();
    576 }
    577 
    578 void TranslateManager::DoTranslatePage(WebContents* web_contents,
    579                                        const std::string& translate_script,
    580                                        const std::string& source_lang,
    581                                        const std::string& target_lang) {
    582   NavigationEntry* entry = web_contents->GetController().GetActiveEntry();
    583   if (!entry) {
    584     NOTREACHED();
    585     return;
    586   }
    587 
    588   TranslateTabHelper* translate_tab_helper =
    589       TranslateTabHelper::FromWebContents(web_contents);
    590   if (!translate_tab_helper)
    591     return;
    592 
    593   translate_tab_helper->language_state().set_translation_pending(true);
    594   web_contents->GetRenderViewHost()->Send(new ChromeViewMsg_TranslatePage(
    595       web_contents->GetRenderViewHost()->GetRoutingID(), entry->GetPageID(),
    596       translate_script, source_lang, target_lang));
    597 }
    598 
    599 void TranslateManager::PageTranslated(WebContents* web_contents,
    600                                       PageTranslatedDetails* details) {
    601   if ((details->error_type == TranslateErrors::NONE) &&
    602       details->source_language != translate::kUnknownLanguageCode &&
    603       !IsSupportedLanguage(details->source_language)) {
    604     details->error_type = TranslateErrors::UNSUPPORTED_LANGUAGE;
    605   }
    606 
    607   if (IsTranslateBubbleEnabled()) {
    608     TranslateBubbleModel::ViewState view_state =
    609         (details->error_type == TranslateErrors::NONE) ?
    610         TranslateBubbleModel::VIEW_STATE_AFTER_TRANSLATE :
    611         TranslateBubbleModel::VIEW_STATE_ERROR;
    612     ShowBubble(web_contents, view_state, details->error_type);
    613   } else {
    614     PrefService* prefs = Profile::FromBrowserContext(
    615         web_contents->GetBrowserContext())->GetPrefs();
    616     TranslateInfoBarDelegate::Create(
    617         true, web_contents,
    618         (details->error_type == TranslateErrors::NONE) ?
    619             TranslateInfoBarDelegate::AFTER_TRANSLATE :
    620             TranslateInfoBarDelegate::TRANSLATION_ERROR,
    621         details->source_language, details->target_language, details->error_type,
    622         prefs, ShortcutConfig());
    623   }
    624 
    625   if (details->error_type != TranslateErrors::NONE &&
    626       !web_contents->GetBrowserContext()->IsOffTheRecord()) {
    627     TranslateErrorDetails error_details;
    628     error_details.time = base::Time::Now();
    629     error_details.url = web_contents->GetLastCommittedURL();
    630     error_details.error = details->error_type;
    631     NotifyTranslateError(error_details);
    632   }
    633 }
    634 
    635 void TranslateManager::FetchLanguageListFromTranslateServer(
    636     PrefService* prefs) {
    637   // We don't want to do this when translate is disabled.
    638   DCHECK(prefs != NULL);
    639   if (CommandLine::ForCurrentProcess()->HasSwitch(
    640       switches::kDisableTranslate) ||
    641       (prefs != NULL && !prefs->GetBoolean(prefs::kEnableTranslate))) {
    642     return;
    643   }
    644 
    645   if (language_list_.get())
    646     language_list_->RequestLanguageList();
    647   else
    648     NOTREACHED();
    649 }
    650 
    651 void TranslateManager::CleanupPendingUlrFetcher() {
    652   language_list_.reset();
    653   script_.reset();
    654 }
    655 
    656 void TranslateManager::OnTranslateScriptFetchComplete(
    657     bool success, const std::string& data) {
    658   std::vector<PendingRequest>::const_iterator iter;
    659   for (iter = pending_requests_.begin(); iter != pending_requests_.end();
    660        ++iter) {
    661     const PendingRequest& request = *iter;
    662     WebContents* web_contents =
    663         tab_util::GetWebContentsByID(request.render_process_id,
    664                                      request.render_view_id);
    665     if (!web_contents) {
    666       // The tab went away while we were retrieving the script.
    667       continue;
    668     }
    669     NavigationEntry* entry = web_contents->GetController().GetActiveEntry();
    670     if (!entry || entry->GetPageID() != request.page_id) {
    671       // We navigated away from the page the translation was triggered on.
    672       continue;
    673     }
    674 
    675     if (success) {
    676       // Translate the page.
    677       const std::string& translate_script = script_->data();
    678       DoTranslatePage(web_contents, translate_script,
    679                       request.source_lang, request.target_lang);
    680     } else {
    681       if (IsTranslateBubbleEnabled()) {
    682         ShowBubble(web_contents, TranslateBubbleModel::VIEW_STATE_ERROR,
    683                    TranslateErrors::NETWORK);
    684       } else {
    685         Profile* profile =
    686             Profile::FromBrowserContext(web_contents->GetBrowserContext());
    687         TranslateInfoBarDelegate::Create(
    688             true, web_contents, TranslateInfoBarDelegate::TRANSLATION_ERROR,
    689             request.source_lang, request.target_lang, TranslateErrors::NETWORK,
    690             profile->GetPrefs(), ShortcutConfig());
    691       }
    692 
    693       if (!web_contents->GetBrowserContext()->IsOffTheRecord()) {
    694         TranslateErrorDetails error_details;
    695         error_details.time = base::Time::Now();
    696         error_details.url = entry->GetURL();
    697         error_details.error = TranslateErrors::NETWORK;
    698         NotifyTranslateError(error_details);
    699       }
    700     }
    701   }
    702   pending_requests_.clear();
    703 }
    704 
    705 void TranslateManager::ShowBubble(WebContents* web_contents,
    706                                   TranslateBubbleModel::ViewState view_state,
    707                                   TranslateErrors::Type error_type) {
    708   // The bubble is implemented only on the desktop platforms.
    709 #if !defined(OS_ANDROID) && !defined(OS_IOS)
    710   Browser* browser = chrome::FindBrowserWithWebContents(web_contents);
    711 
    712   // |browser| might be NULL when testing. In this case, Show(...) should be
    713   // called because the implementation for testing is used.
    714   if (!browser) {
    715     TranslateBubbleFactory::Show(NULL, web_contents, view_state, error_type);
    716     return;
    717   }
    718 
    719   if (web_contents != browser->tab_strip_model()->GetActiveWebContents())
    720     return;
    721 
    722   // This ShowBubble function is also used for upating the existing bubble.
    723   // However, with the bubble shown, any browser windows are NOT activated
    724   // because the bubble takes the focus from the other widgets including the
    725   // browser windows. So it is checked that |browser| is the last activated
    726   // browser, not is now activated.
    727   if (browser !=
    728       chrome::FindLastActiveWithHostDesktopType(browser->host_desktop_type())) {
    729     return;
    730   }
    731 
    732   // During auto-translating, the bubble should not be shown.
    733   if (view_state == TranslateBubbleModel::VIEW_STATE_TRANSLATING ||
    734       view_state == TranslateBubbleModel::VIEW_STATE_AFTER_TRANSLATE) {
    735     TranslateTabHelper* translate_tab_helper =
    736         TranslateTabHelper::FromWebContents(web_contents);
    737     if (!translate_tab_helper ||
    738         translate_tab_helper->language_state().InTranslateNavigation()) {
    739       return;
    740     }
    741   }
    742 
    743   TranslateBubbleFactory::Show(browser->window(), web_contents, view_state,
    744                                error_type);
    745 #else
    746   NOTREACHED();
    747 #endif
    748 }
    749 
    750 // static
    751 std::string TranslateManager::GetTargetLanguage(PrefService* prefs) {
    752   std::string ui_lang =
    753       TranslatePrefs::ConvertLangCodeForTranslation(
    754           GetLanguageCode(g_browser_process->GetApplicationLocale()));
    755 
    756   if (IsSupportedLanguage(ui_lang))
    757     return ui_lang;
    758 
    759   // Getting the accepted languages list
    760   std::string accept_langs_str = prefs->GetString(prefs::kAcceptLanguages);
    761 
    762   std::vector<std::string> accept_langs_list;
    763   base::SplitString(accept_langs_str, ',', &accept_langs_list);
    764 
    765   // Will translate to the first supported language on the Accepted Language
    766   // list or not at all if no such candidate exists
    767   std::vector<std::string>::iterator iter;
    768   for (iter = accept_langs_list.begin();
    769        iter != accept_langs_list.end(); ++iter) {
    770     std::string lang_code = GetLanguageCode(*iter);
    771     if (IsSupportedLanguage(lang_code))
    772       return lang_code;
    773   }
    774   return std::string();
    775 }
    776 
    777 // static
    778 std::string TranslateManager::GetAutoTargetLanguage(
    779     const std::string& original_language,
    780     PrefService* prefs) {
    781   std::string auto_target_lang;
    782   if (TranslatePrefs::ShouldAutoTranslate(prefs, original_language,
    783                                           &auto_target_lang)) {
    784     // We need to confirm that the saved target language is still supported.
    785     // Also, GetLanguageCode will take care of removing country code if any.
    786     auto_target_lang = GetLanguageCode(auto_target_lang);
    787     if (IsSupportedLanguage(auto_target_lang))
    788       return auto_target_lang;
    789   }
    790   return std::string();
    791 }
    792 
    793 // static
    794 bool TranslateManager::IsTranslateBubbleEnabled() {
    795   if (CommandLine::ForCurrentProcess()->HasSwitch(
    796           switches::kEnableTranslateNewUX)) {
    797     return true;
    798   }
    799 
    800   std::string group_name = base::FieldTrialList::FindFullName(
    801       kFieldTrialNameForUX);
    802   return group_name == "Bubble";
    803 }
    804 
    805 // static
    806 ShortcutConfiguration TranslateManager::ShortcutConfig() {
    807   ShortcutConfiguration config;
    808 
    809   // The android implementation does not offer a drop down (for space reasons),
    810   // so we are more aggressive about showing the shortcut to never translate.
    811   #if defined(OS_ANDROID)
    812   config.never_translate_min_count = 1;
    813   #else
    814   config.never_translate_min_count = 3;
    815   #endif  // defined(OS_ANDROID)
    816 
    817   config.always_translate_min_count = 3;
    818   return config;
    819 }
    820