translate_manager.cc revision f60fc993c7b081abf77ce2ffc7fcca1142c8cb01
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
62using content::NavigationController;
63using content::NavigationEntry;
64using content::WebContents;
65
66namespace {
67
68const 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.
73const char kSourceLanguageQueryName[] = "sl";
74
75// Used in kReportLanguageDetectionErrorURL to specify the page URL.
76const 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
80const int kMaxTranslateLoadCheckAttempts = 20;
81
82// The field trial name to compare Translate infobar and bubble.
83const char kFieldTrialNameForUX[] = "TranslateInfobarVsBubble";
84
85}  // namespace
86
87TranslateManager::~TranslateManager() {
88}
89
90// static
91TranslateManager* TranslateManager::GetInstance() {
92  return Singleton<TranslateManager>::get();
93}
94
95// static
96bool 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
115void 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
125base::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
134std::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
143bool 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
151bool 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
159bool 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
169void TranslateManager::SetTranslateScriptExpirationDelay(int delay_ms) {
170  if (script_.get() == NULL) {
171    NOTREACHED();
172    return;
173  }
174  script_->set_expiration_delay(delay_ms);
175}
176
177void 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
274void TranslateManager::AddObserver(Observer* obs) {
275  observer_list_.AddObserver(obs);
276}
277
278void TranslateManager::RemoveObserver(Observer* obs) {
279  observer_list_.RemoveObserver(obs);
280}
281
282void TranslateManager::NotifyTranslateEvent(
283    const TranslateEventDetails& details) {
284  FOR_EACH_OBSERVER(Observer, observer_list_, OnTranslateEvent(details));
285}
286
287void TranslateManager::NotifyLanguageDetection(
288    const LanguageDetectionDetails& details) {
289  FOR_EACH_OBSERVER(Observer, observer_list_, OnLanguageDetection(details));
290}
291
292void TranslateManager::NotifyTranslateError(
293    const TranslateErrorDetails& details) {
294  FOR_EACH_OBSERVER(Observer, observer_list_, OnTranslateError(details));
295}
296
297TranslateManager::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
312void 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
436void 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
468void 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
524void 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
539void 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
570void TranslateManager::ClearTranslateScript() {
571  if (script_.get() == NULL) {
572    NOTREACHED();
573    return;
574  }
575  script_->Clear();
576}
577
578void 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
599void 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
635void 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
651void TranslateManager::CleanupPendingUlrFetcher() {
652  language_list_.reset();
653  script_.reset();
654}
655
656void 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
705void 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  TranslateBubbleFactory::Show(browser->window(), web_contents, view_state,
733                               error_type);
734#else
735  NOTREACHED();
736#endif
737}
738
739// static
740std::string TranslateManager::GetTargetLanguage(PrefService* prefs) {
741  std::string ui_lang =
742      TranslatePrefs::ConvertLangCodeForTranslation(
743          GetLanguageCode(g_browser_process->GetApplicationLocale()));
744
745  if (IsSupportedLanguage(ui_lang))
746    return ui_lang;
747
748  // Getting the accepted languages list
749  std::string accept_langs_str = prefs->GetString(prefs::kAcceptLanguages);
750
751  std::vector<std::string> accept_langs_list;
752  base::SplitString(accept_langs_str, ',', &accept_langs_list);
753
754  // Will translate to the first supported language on the Accepted Language
755  // list or not at all if no such candidate exists
756  std::vector<std::string>::iterator iter;
757  for (iter = accept_langs_list.begin();
758       iter != accept_langs_list.end(); ++iter) {
759    std::string lang_code = GetLanguageCode(*iter);
760    if (IsSupportedLanguage(lang_code))
761      return lang_code;
762  }
763  return std::string();
764}
765
766// static
767std::string TranslateManager::GetAutoTargetLanguage(
768    const std::string& original_language,
769    PrefService* prefs) {
770  std::string auto_target_lang;
771  if (TranslatePrefs::ShouldAutoTranslate(prefs, original_language,
772                                          &auto_target_lang)) {
773    // We need to confirm that the saved target language is still supported.
774    // Also, GetLanguageCode will take care of removing country code if any.
775    auto_target_lang = GetLanguageCode(auto_target_lang);
776    if (IsSupportedLanguage(auto_target_lang))
777      return auto_target_lang;
778  }
779  return std::string();
780}
781
782// static
783bool TranslateManager::IsTranslateBubbleEnabled() {
784  if (CommandLine::ForCurrentProcess()->HasSwitch(
785          switches::kEnableTranslateNewUX)) {
786    return true;
787  }
788
789  std::string group_name = base::FieldTrialList::FindFullName(
790      kFieldTrialNameForUX);
791  return group_name == "Bubble";
792}
793
794// static
795ShortcutConfiguration TranslateManager::ShortcutConfig() {
796  ShortcutConfiguration config;
797
798  // The android implementation does not offer a drop down (for space reasons),
799  // so we are more aggressive about showing the shortcut to never translate.
800  #if defined(OS_ANDROID)
801  config.never_translate_min_count = 1;
802  #else
803  config.never_translate_min_count = 3;
804  #endif  // defined(OS_ANDROID)
805
806  config.always_translate_min_count = 3;
807  return config;
808}
809