translate_manager.cc revision 5821806d5e7f356e8fa4b058a389a808ea183019
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/compiler_specific.h"
10#include "base/json/json_reader.h"
11#include "base/memory/singleton.h"
12#include "base/message_loop.h"
13#include "base/metrics/histogram.h"
14#include "base/string_split.h"
15#include "base/string_util.h"
16#include "base/stringprintf.h"
17#include "base/values.h"
18#include "chrome/browser/browser_process.h"
19#include "chrome/browser/infobars/infobar_tab_helper.h"
20#include "chrome/browser/prefs/pref_service.h"
21#include "chrome/browser/profiles/profile.h"
22#include "chrome/browser/tab_contents/language_state.h"
23#include "chrome/browser/tab_contents/tab_util.h"
24#include "chrome/browser/translate/page_translated_details.h"
25#include "chrome/browser/translate/translate_infobar_delegate.h"
26#include "chrome/browser/translate/translate_prefs.h"
27#include "chrome/browser/translate/translate_tab_helper.h"
28#include "chrome/browser/ui/browser.h"
29#include "chrome/browser/ui/browser_finder.h"
30#include "chrome/browser/ui/browser_tabstrip.h"
31#include "chrome/browser/ui/tabs/tab_strip_model.h"
32#include "chrome/common/chrome_notification_types.h"
33#include "chrome/common/chrome_switches.h"
34#include "chrome/common/pref_names.h"
35#include "chrome/common/render_messages.h"
36#include "chrome/common/translate_errors.h"
37#include "chrome/common/url_constants.h"
38#include "content/public/browser/navigation_controller.h"
39#include "content/public/browser/navigation_details.h"
40#include "content/public/browser/navigation_entry.h"
41#include "content/public/browser/notification_details.h"
42#include "content/public/browser/notification_service.h"
43#include "content/public/browser/notification_source.h"
44#include "content/public/browser/notification_types.h"
45#include "content/public/browser/render_process_host.h"
46#include "content/public/browser/render_view_host.h"
47#include "content/public/browser/web_contents.h"
48#include "google_apis/google_api_keys.h"
49#include "grit/browser_resources.h"
50#include "net/base/escape.h"
51#include "net/base/load_flags.h"
52#include "net/url_request/url_fetcher.h"
53#include "net/url_request/url_request_status.h"
54#include "ui/base/resource/resource_bundle.h"
55
56#ifdef FILE_MANAGER_EXTENSION
57#include "chrome/browser/chromeos/extensions/file_manager_util.h"
58#endif
59
60using content::NavigationController;
61using content::NavigationEntry;
62using content::WebContents;
63
64namespace {
65
66// The default list of languages the Google translation server supports.
67// We use this list until we receive the list that the server exposes.
68// For information, here is the list of languages that Chrome can be run in
69// but that the translation server does not support:
70// am Amharic
71// bn Bengali
72// gu Gujarati
73// kn Kannada
74// ml Malayalam
75// mr Marathi
76// ta Tamil
77// te Telugu
78const char* const kDefaultSupportedLanguages[] = {
79    "af",     // Afrikaans
80    "az",     // Azerbaijani
81    "sq",     // Albanian
82    "ar",     // Arabic
83    "hy",     // Armenian
84    "eu",     // Basque
85    "be",     // Belarusian
86    "bg",     // Bulgarian
87    "ca",     // Catalan
88    "zh-CN",  // Chinese (Simplified)
89    "zh-TW",  // Chinese (Traditional)
90    "hr",     // Croatian
91    "cs",     // Czech
92    "da",     // Danish
93    "nl",     // Dutch
94    "en",     // English
95    "et",     // Estonian
96    "fi",     // Finnish
97    "fil",    // Filipino
98    "fr",     // French
99    "gl",     // Galician
100    "de",     // German
101    "el",     // Greek
102    "ht",     // Haitian Creole
103    "he",     // Hebrew
104    "iw",     // Hebrew Synonym
105    "hi",     // Hindi
106    "hu",     // Hungarian
107    "is",     // Icelandic
108    "id",     // Indonesian
109    "it",     // Italian
110    "ga",     // Irish
111    "ja",     // Japanese
112    "ka",     // Georgian
113    "ko",     // Korean
114    "lv",     // Latvian
115    "lt",     // Lithuanian
116    "mk",     // Macedonian
117    "ms",     // Malay
118    "mt",     // Maltese
119    "nb",     // Norwegian
120    "no",     // Norwegian synonym
121    "fa",     // Persian
122    "pl",     // Polish
123    "pt",     // Portuguese
124    "ro",     // Romanian
125    "ru",     // Russian
126    "sr",     // Serbian
127    "sk",     // Slovak
128    "sl",     // Slovenian
129    "es",     // Spanish
130    "sw",     // Swahili
131    "sv",     // Swedish
132    "th",     // Thai
133    "tr",     // Turkish
134    "uk",     // Ukrainian
135    "ur",     // Urdu
136    "vi",     // Vietnamese
137    "cy",     // Welsh
138    "yi",     // Yiddish
139};
140
141// Language code synonyms. Some languages have changed codes over the years
142// and sometimes the older codes are used, so we must see them as synonyms.
143// Duplicated in TranslateManagerTest::LanguageCodeSynonyms.
144// Note that we use code_1 and code_2 as opposed to old/new because of cases
145// where both codes are still valid (like no & nb) but we still treat them
146// as the same since they are close enough from the translate server point of
147// view.
148struct LanguageCodeSynonym {
149  const char* const code_1;
150  const char* const code_2;
151};
152
153const LanguageCodeSynonym kLanguageCodeSynonyms[] = {
154  {"no", "nb"},
155  {"iw", "he"},
156  {"jw", "jv"},
157};
158
159const char* const kTranslateScriptURL =
160    "https://translate.google.com/translate_a/element.js?"
161    "cb=cr.googleTranslate.onTranslateElementLoad&hl=%s";
162const char* const kTranslateScriptHeader =
163    "Google-Translate-Element-Mode: library";
164const char* const kReportLanguageDetectionErrorURL =
165    "https://translate.google.com/translate_error";
166const char* const kLanguageListFetchURL =
167    "https://translate.googleapis.com/translate_a/l?client=chrome&cb=sl&hl=%s";
168const int kMaxRetryLanguageListFetch = 5;
169const int kTranslateScriptExpirationDelayDays = 1;
170
171void AddApiKeyToUrl(GURL* url) {
172  std::string api_key = google_apis::GetAPIKey();
173  std::string query(url->query());
174  if (!query.empty())
175    query += "&";
176  query += "key=" + net::EscapeQueryParamValue(api_key, true);
177  GURL::Replacements replacements;
178  replacements.SetQueryStr(query);
179  *url = url->ReplaceComponents(replacements);
180}
181
182}  // namespace
183
184// This must be kept in sync with the &cb= value in the kLanguageListFetchURL.
185const char* const TranslateManager::kLanguageListCallbackName = "sl(";
186const char* const TranslateManager::kTargetLanguagesKey = "tl";
187
188// static
189base::LazyInstance<std::set<std::string> >
190    TranslateManager::supported_languages_ = LAZY_INSTANCE_INITIALIZER;
191
192TranslateManager::~TranslateManager() {
193  weak_method_factory_.InvalidateWeakPtrs();
194}
195
196// static
197TranslateManager* TranslateManager::GetInstance() {
198  return Singleton<TranslateManager>::get();
199}
200
201// static
202bool TranslateManager::IsTranslatableURL(const GURL& url) {
203  // A URLs is translatable unless it is one of the following:
204  // - an internal URL (chrome:// and others)
205  // - the devtools (which is considered UI)
206  // - an FTP page (as FTP pages tend to have long lists of filenames that may
207  //   confuse the CLD)
208  return !url.SchemeIs(chrome::kChromeUIScheme) &&
209         !url.SchemeIs(chrome::kChromeDevToolsScheme) &&
210         !url.SchemeIs(chrome::kFtpScheme);
211}
212
213// static
214void TranslateManager::SetSupportedLanguages(const std::string& language_list) {
215  // The format is:
216  // sl({'sl': {'XX': 'LanguageName', ...}, 'tl': {'XX': 'LanguageName', ...}})
217  // Where "sl(" is set in kLanguageListCallbackName
218  // and 'tl' is kTargetLanguagesKey
219  if (!StartsWithASCII(language_list, kLanguageListCallbackName, false) ||
220      !EndsWith(language_list, ")", false)) {
221    // We don't have a NOTREACHED here since this can happen in ui_tests, even
222    // though the the BrowserMain function won't call us with parameters.ui_task
223    // is NULL some tests don't set it, so we must bail here.
224    return;
225  }
226  static const size_t kLanguageListCallbackNameLength =
227      strlen(kLanguageListCallbackName);
228  std::string languages_json = language_list.substr(
229      kLanguageListCallbackNameLength,
230      language_list.size() - kLanguageListCallbackNameLength - 1);
231  // JSON doesn't support single quotes though this is what is used on the
232  // translate server so we must replace them with double quotes.
233  ReplaceSubstringsAfterOffset(&languages_json, 0, "'", "\"");
234  scoped_ptr<Value> json_value(
235      base::JSONReader::Read(languages_json, base::JSON_ALLOW_TRAILING_COMMAS));
236  if (json_value == NULL || !json_value->IsType(Value::TYPE_DICTIONARY)) {
237    NOTREACHED();
238    return;
239  }
240  // The first level dictionary contains two sub-dict, one for source languages
241  // and the other for target languages, we want to use the target languages.
242  DictionaryValue* language_dict =
243      static_cast<DictionaryValue*>(json_value.get());
244  DictionaryValue* target_languages = NULL;
245  if (!language_dict->GetDictionary(kTargetLanguagesKey, &target_languages) ||
246      target_languages == NULL) {
247    NOTREACHED();
248    return;
249  }
250  // Now we can clear our current state...
251  std::set<std::string>* supported_languages = supported_languages_.Pointer();
252  supported_languages->clear();
253  // ... and replace it with the values we just fetched from the server.
254  DictionaryValue::key_iterator iter = target_languages->begin_keys();
255  for (; iter != target_languages->end_keys(); ++iter)
256    supported_languages_.Pointer()->insert(*iter);
257
258  // Now add synonyms if one and only one of the pair element is in the list...
259  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kLanguageCodeSynonyms); ++i) {
260    if (supported_languages_.Pointer()->count(
261            kLanguageCodeSynonyms[i].code_1) != 0) {
262      if (supported_languages_.Pointer()->count(
263              kLanguageCodeSynonyms[i].code_2) == 0) {
264        supported_languages_.Pointer()->insert(
265            kLanguageCodeSynonyms[i].code_2);
266      }
267    } else if (supported_languages_.Pointer()->count(
268                  kLanguageCodeSynonyms[i].code_2) != 0) {
269      supported_languages_.Pointer()->insert(
270            kLanguageCodeSynonyms[i].code_1);
271    }
272  }
273}
274
275// static
276void TranslateManager::InitSupportedLanguages() {
277  // If our list of supported languages have not been set yet, we default
278  // to our hard coded list of languages in kDefaultSupportedLanguages.
279  if (supported_languages_.Pointer()->empty()) {
280    for (size_t i = 0; i < arraysize(kDefaultSupportedLanguages); ++i)
281      supported_languages_.Pointer()->insert(kDefaultSupportedLanguages[i]);
282  }
283}
284
285// static
286void TranslateManager::GetSupportedLanguages(
287    std::vector<std::string>* languages) {
288  DCHECK(languages && languages->empty());
289  InitSupportedLanguages();
290  std::set<std::string>* supported_languages = supported_languages_.Pointer();
291  std::set<std::string>::const_iterator iter = supported_languages->begin();
292  for (; iter != supported_languages->end(); ++iter)
293    languages->push_back(*iter);
294}
295
296// static
297std::string TranslateManager::GetLanguageCode(
298    const std::string& chrome_locale) {
299  // Only remove the country code for country specific languages we don't
300  // support specifically yet.
301  if (IsSupportedLanguage(chrome_locale))
302    return chrome_locale;
303
304  size_t hypen_index = chrome_locale.find('-');
305  if (hypen_index == std::string::npos)
306    return chrome_locale;
307  return chrome_locale.substr(0, hypen_index);
308}
309
310// static
311bool TranslateManager::IsSupportedLanguage(const std::string& page_language) {
312  InitSupportedLanguages();
313  return supported_languages_.Pointer()->count(page_language) != 0;
314}
315
316void TranslateManager::Observe(int type,
317                               const content::NotificationSource& source,
318                               const content::NotificationDetails& details) {
319  switch (type) {
320    case content::NOTIFICATION_NAV_ENTRY_COMMITTED: {
321      NavigationController* controller =
322          content::Source<NavigationController>(source).ptr();
323      content::LoadCommittedDetails* load_details =
324          content::Details<content::LoadCommittedDetails>(details).ptr();
325      NavigationEntry* entry = controller->GetActiveEntry();
326      if (!entry) {
327        NOTREACHED();
328        return;
329      }
330
331      TranslateTabHelper* translate_tab_helper =
332          TranslateTabHelper::FromWebContents(controller->GetWebContents());
333      if (!translate_tab_helper)
334        return;
335
336      if (!load_details->is_main_frame &&
337          translate_tab_helper->language_state().translation_declined()) {
338        // Some sites (such as Google map) may trigger sub-frame navigations
339        // when the user interacts with the page.  We don't want to show a new
340        // infobar if the user already dismissed one in that case.
341        return;
342      }
343      if (entry->GetTransitionType() != content::PAGE_TRANSITION_RELOAD &&
344          load_details->type != content::NAVIGATION_TYPE_SAME_PAGE) {
345        return;
346      }
347      // When doing a page reload, TAB_LANGUAGE_DETERMINED is not sent,
348      // so the translation needs to be explicitly initiated, but only when the
349      // page is translatable.
350      if (!translate_tab_helper->language_state().page_translatable())
351        return;
352      // Note that we delay it as the TranslateManager gets this notification
353      // before the WebContents and the WebContents processing might remove the
354      // current infobars.  Since InitTranslation might add an infobar, it must
355      // be done after that.
356      MessageLoop::current()->PostTask(FROM_HERE,
357          base::Bind(
358              &TranslateManager::InitiateTranslationPosted,
359              weak_method_factory_.GetWeakPtr(),
360              controller->GetWebContents()->GetRenderProcessHost()->GetID(),
361              controller->GetWebContents()->GetRenderViewHost()->GetRoutingID(),
362              translate_tab_helper->language_state().original_language()));
363      break;
364    }
365    case chrome::NOTIFICATION_TAB_LANGUAGE_DETERMINED: {
366      WebContents* tab = content::Source<WebContents>(source).ptr();
367      // We may get this notifications multiple times.  Make sure to translate
368      // only once.
369      TranslateTabHelper* translate_tab_helper =
370          TranslateTabHelper::FromWebContents(tab);
371      if (!translate_tab_helper)
372        return;
373
374      LanguageState& language_state = translate_tab_helper->language_state();
375      if (language_state.page_translatable() &&
376          !language_state.translation_pending() &&
377          !language_state.translation_declined() &&
378          !language_state.IsPageTranslated()) {
379        std::string language = *(content::Details<std::string>(details).ptr());
380        InitiateTranslation(tab, language);
381      }
382      break;
383    }
384    case chrome::NOTIFICATION_PAGE_TRANSLATED: {
385      // Only add translate infobar if it doesn't exist; if it already exists,
386      // just update the state, the actual infobar would have received the same
387      //  notification and update the visual display accordingly.
388      WebContents* tab = content::Source<WebContents>(source).ptr();
389      PageTranslatedDetails* page_translated_details =
390          content::Details<PageTranslatedDetails>(details).ptr();
391      PageTranslated(tab, page_translated_details);
392      break;
393    }
394    case chrome::NOTIFICATION_PROFILE_DESTROYED: {
395      PrefService* pref_service =
396          content::Source<Profile>(source).ptr()->GetPrefs();
397      notification_registrar_.Remove(this,
398                                     chrome::NOTIFICATION_PROFILE_DESTROYED,
399                                     source);
400      size_t count = accept_languages_.erase(pref_service);
401      // We should know about this profile since we are listening for
402      // notifications on it.
403      DCHECK(count == 1u);
404      PrefChangeRegistrar* pref_change_registrar =
405          pref_change_registrars_[pref_service];
406      count = pref_change_registrars_.erase(pref_service);
407      DCHECK(count == 1u);
408      delete pref_change_registrar;
409      break;
410    }
411    default:
412      NOTREACHED();
413  }
414}
415
416void TranslateManager::OnPreferenceChanged(PrefServiceBase* service,
417                                           const std::string& pref_name) {
418  DCHECK_EQ(std::string(prefs::kAcceptLanguages), pref_name);
419  InitAcceptLanguages(service);
420}
421
422void TranslateManager::OnURLFetchComplete(const net::URLFetcher* source) {
423  if (translate_script_request_pending_.get() != source &&
424      language_list_request_pending_.get() != source) {
425    // Looks like crash on Mac is possibly caused with callback entering here
426    // with unknown fetcher when network is refreshed.
427    scoped_ptr<const net::URLFetcher> delete_ptr(source);
428    return;
429  }
430
431  bool error =
432      (source->GetStatus().status() != net::URLRequestStatus::SUCCESS ||
433      source->GetResponseCode() != 200);
434  if (translate_script_request_pending_.get() == source) {
435    scoped_ptr<const net::URLFetcher> delete_ptr(
436        translate_script_request_pending_.release());
437    if (!error) {
438      base::StringPiece str = ResourceBundle::GetSharedInstance().
439          GetRawDataResource(IDR_TRANSLATE_JS);
440      DCHECK(translate_script_.empty());
441      str.CopyToString(&translate_script_);
442      std::string data;
443      source->GetResponseAsString(&data);
444      translate_script_ += "\n" + data;
445      // We'll expire the cached script after some time, to make sure long
446      // running browsers still get fixes that might get pushed with newer
447      // scripts.
448      MessageLoop::current()->PostDelayedTask(FROM_HERE,
449          base::Bind(&TranslateManager::ClearTranslateScript,
450                     weak_method_factory_.GetWeakPtr()),
451          translate_script_expiration_delay_);
452    }
453    // Process any pending requests.
454    std::vector<PendingRequest>::const_iterator iter;
455    for (iter = pending_requests_.begin(); iter != pending_requests_.end();
456         ++iter) {
457      const PendingRequest& request = *iter;
458      WebContents* web_contents =
459          tab_util::GetWebContentsByID(request.render_process_id,
460                                       request.render_view_id);
461      if (!web_contents) {
462        // The tab went away while we were retrieving the script.
463        continue;
464      }
465      NavigationEntry* entry = web_contents->GetController().GetActiveEntry();
466      if (!entry || entry->GetPageID() != request.page_id) {
467        // We navigated away from the page the translation was triggered on.
468        continue;
469      }
470
471      if (error) {
472        Profile* profile =
473            Profile::FromBrowserContext(web_contents->GetBrowserContext());
474        InfoBarTabHelper* infobar_helper =
475            InfoBarTabHelper::FromWebContents(web_contents);
476        ShowInfoBar(
477            web_contents,
478            TranslateInfoBarDelegate::CreateErrorDelegate(
479                TranslateErrors::NETWORK,
480                infobar_helper,
481                profile->GetPrefs(),
482                request.source_lang,
483                request.target_lang));
484      } else {
485        // Translate the page.
486        DoTranslatePage(web_contents, translate_script_,
487                        request.source_lang, request.target_lang);
488      }
489    }
490    pending_requests_.clear();
491  } else {  // if (translate_script_request_pending_.get() == source)
492    scoped_ptr<const net::URLFetcher> delete_ptr(
493        language_list_request_pending_.release());
494    if (!error) {
495      std::string data;
496      source->GetResponseAsString(&data);
497      SetSupportedLanguages(data);
498    } else {
499      VLOG(9) << "Failed to Fetch languages from: " << kLanguageListFetchURL;
500    }
501  }
502}
503
504// static
505bool TranslateManager::IsShowingTranslateInfobar(WebContents* web_contents) {
506  return GetTranslateInfoBarDelegate(web_contents) != NULL;
507}
508
509TranslateManager::TranslateManager()
510    : ALLOW_THIS_IN_INITIALIZER_LIST(weak_method_factory_(this)),
511      translate_script_expiration_delay_(
512          base::TimeDelta::FromDays(kTranslateScriptExpirationDelayDays)) {
513  notification_registrar_.Add(this, content::NOTIFICATION_NAV_ENTRY_COMMITTED,
514                              content::NotificationService::AllSources());
515  notification_registrar_.Add(this,
516                              chrome::NOTIFICATION_TAB_LANGUAGE_DETERMINED,
517                              content::NotificationService::AllSources());
518  notification_registrar_.Add(this, chrome::NOTIFICATION_PAGE_TRANSLATED,
519                              content::NotificationService::AllSources());
520}
521
522void TranslateManager::InitiateTranslation(WebContents* web_contents,
523                                           const std::string& page_lang) {
524#ifdef FILE_MANAGER_EXTENSION
525  const GURL& page_url = web_contents->GetURL();
526  if (page_url.SchemeIs("chrome-extension") &&
527      page_url.DomainIs(kFileBrowserDomain))
528    return;
529#endif
530
531  Profile* profile =
532      Profile::FromBrowserContext(web_contents->GetBrowserContext());
533  PrefService* prefs = profile->GetOriginalProfile()->GetPrefs();
534  if (!prefs->GetBoolean(prefs::kEnableTranslate))
535    return;
536
537  // Allow disabling of translate from the command line to assist with
538  // automated browser testing.
539  if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kDisableTranslate))
540    return;
541
542  NavigationEntry* entry = web_contents->GetController().GetActiveEntry();
543  if (!entry) {
544    // This can happen for popups created with window.open("").
545    return;
546  }
547
548  // If there is already a translate infobar showing, don't show another one.
549  if (GetTranslateInfoBarDelegate(web_contents))
550    return;
551
552  std::string target_lang = GetTargetLanguage(prefs);
553  std::string language_code = GetLanguageCode(page_lang);
554  // Nothing to do if either the language Chrome is in or the language of the
555  // page is not supported by the translation server.
556  if (target_lang.empty() || !IsSupportedLanguage(language_code)) {
557    return;
558  }
559
560  // We don't want to translate:
561  // - any Chrome specific page (New Tab Page, Download, History... pages).
562  // - similar languages (ex: en-US to en).
563  // - any user black-listed URLs or user selected language combination.
564  // - any language the user configured as accepted languages.
565  if (!IsTranslatableURL(entry->GetURL()) || language_code == target_lang ||
566      !TranslatePrefs::CanTranslate(prefs, language_code, entry->GetURL()) ||
567      IsAcceptLanguage(web_contents, language_code)) {
568    return;
569  }
570
571  // If the user has previously selected "always translate" for this language we
572  // automatically translate.  Note that in incognito mode we disable that
573  // feature; the user will get an infobar, so they can control whether the
574  // page's text is sent to the translate server.
575  std::string auto_target_lang;
576  if (!web_contents->GetBrowserContext()->IsOffTheRecord() &&
577      TranslatePrefs::ShouldAutoTranslate(prefs, language_code,
578          &auto_target_lang)) {
579    // We need to confirm that the saved target language is still supported.
580    // Also, GetLanguageCode will take care of removing country code if any.
581    auto_target_lang = GetLanguageCode(auto_target_lang);
582    if (IsSupportedLanguage(auto_target_lang)) {
583      TranslatePage(web_contents, language_code, auto_target_lang);
584      return;
585    }
586  }
587
588  TranslateTabHelper* translate_tab_helper =
589      TranslateTabHelper::FromWebContents(web_contents);
590  if (!translate_tab_helper)
591    return;
592
593  std::string auto_translate_to =
594      translate_tab_helper->language_state().AutoTranslateTo();
595  if (!auto_translate_to.empty()) {
596    // This page was navigated through a click from a translated page.
597    TranslatePage(web_contents, language_code, auto_translate_to);
598    return;
599  }
600
601  InfoBarTabHelper* infobar_helper =
602      InfoBarTabHelper::FromWebContents(web_contents);
603  // Prompts the user if he/she wants the page translated.
604  infobar_helper->AddInfoBar(
605      TranslateInfoBarDelegate::CreateDelegate(
606          TranslateInfoBarDelegate::BEFORE_TRANSLATE, infobar_helper,
607          profile->GetPrefs(), language_code, target_lang));
608}
609
610void TranslateManager::InitiateTranslationPosted(
611    int process_id, int render_id, const std::string& page_lang) {
612  // The tab might have been closed.
613  WebContents* web_contents =
614      tab_util::GetWebContentsByID(process_id, render_id);
615  if (!web_contents)
616    return;
617
618  TranslateTabHelper* translate_tab_helper =
619      TranslateTabHelper::FromWebContents(web_contents);
620  if (translate_tab_helper->language_state().translation_pending())
621    return;
622
623  InitiateTranslation(web_contents, GetLanguageCode(page_lang));
624}
625
626void TranslateManager::TranslatePage(WebContents* web_contents,
627                                     const std::string& source_lang,
628                                     const std::string& target_lang) {
629  NavigationEntry* entry = web_contents->GetController().GetActiveEntry();
630  if (!entry) {
631    NOTREACHED();
632    return;
633  }
634
635  Profile* profile =
636      Profile::FromBrowserContext(web_contents->GetBrowserContext());
637  InfoBarTabHelper* infobar_helper =
638      InfoBarTabHelper::FromWebContents(web_contents);
639  ShowInfoBar(web_contents, TranslateInfoBarDelegate::CreateDelegate(
640      TranslateInfoBarDelegate::TRANSLATING, infobar_helper,
641      profile->GetPrefs(), source_lang, target_lang));
642
643  if (!translate_script_.empty()) {
644    DoTranslatePage(web_contents, translate_script_, source_lang, target_lang);
645    return;
646  }
647
648  // The script is not available yet.  Queue that request and query for the
649  // script.  Once it is downloaded we'll do the translate.
650  content::RenderViewHost* rvh = web_contents->GetRenderViewHost();
651  PendingRequest request;
652  request.render_process_id = rvh->GetProcess()->GetID();
653  request.render_view_id = rvh->GetRoutingID();
654  request.page_id = entry->GetPageID();
655  request.source_lang = source_lang;
656  request.target_lang = target_lang;
657  pending_requests_.push_back(request);
658  RequestTranslateScript();
659}
660
661void TranslateManager::RevertTranslation(WebContents* web_contents) {
662  NavigationEntry* entry = web_contents->GetController().GetActiveEntry();
663  if (!entry) {
664    NOTREACHED();
665    return;
666  }
667  web_contents->GetRenderViewHost()->Send(new ChromeViewMsg_RevertTranslation(
668      web_contents->GetRenderViewHost()->GetRoutingID(), entry->GetPageID()));
669
670  TranslateTabHelper* translate_tab_helper =
671      TranslateTabHelper::FromWebContents(web_contents);
672  translate_tab_helper->language_state().set_current_language(
673      translate_tab_helper->language_state().original_language());
674}
675
676void TranslateManager::ReportLanguageDetectionError(WebContents* web_contents) {
677  UMA_HISTOGRAM_COUNTS("Translate.ReportLanguageDetectionError", 1);
678  // We'll open the URL in a new tab so that the user can tell us more.
679  Browser* browser = browser::FindBrowserWithWebContents(web_contents);
680  if (!browser) {
681    NOTREACHED();
682    return;
683  }
684
685  GURL page_url = web_contents->GetController().GetActiveEntry()->GetURL();
686  // Report option should be disabled for secure URLs.
687  DCHECK(!page_url.SchemeIsSecure());
688  std::string report_error_url_str(kReportLanguageDetectionErrorURL);
689  report_error_url_str += "?client=cr&action=langidc&u=";
690  report_error_url_str += net::EscapeUrlEncodedData(page_url.spec(), true);
691  report_error_url_str += "&sl=";
692
693  TranslateTabHelper* translate_tab_helper =
694      TranslateTabHelper::FromWebContents(web_contents);
695  report_error_url_str +=
696      translate_tab_helper->language_state().original_language();
697  report_error_url_str += "&hl=";
698  report_error_url_str +=
699      GetLanguageCode(g_browser_process->GetApplicationLocale());
700
701  GURL report_error_url(report_error_url_str);
702  AddApiKeyToUrl(&report_error_url);
703  chrome::AddSelectedTabWithURL(browser, report_error_url,
704                                content::PAGE_TRANSITION_AUTO_BOOKMARK);
705}
706
707void TranslateManager::DoTranslatePage(WebContents* web_contents,
708                                       const std::string& translate_script,
709                                       const std::string& source_lang,
710                                       const std::string& target_lang) {
711  NavigationEntry* entry = web_contents->GetController().GetActiveEntry();
712  if (!entry) {
713    NOTREACHED();
714    return;
715  }
716
717  TranslateTabHelper* translate_tab_helper =
718      TranslateTabHelper::FromWebContents(web_contents);
719  if (!translate_tab_helper)
720    return;
721
722  translate_tab_helper->language_state().set_translation_pending(true);
723  web_contents->GetRenderViewHost()->Send(new ChromeViewMsg_TranslatePage(
724      web_contents->GetRenderViewHost()->GetRoutingID(), entry->GetPageID(),
725      translate_script, source_lang, target_lang));
726}
727
728void TranslateManager::PageTranslated(WebContents* web_contents,
729                                      PageTranslatedDetails* details) {
730  InfoBarTabHelper* infobar_helper =
731      InfoBarTabHelper::FromWebContents(web_contents);
732  Profile* profile =
733      Profile::FromBrowserContext(web_contents->GetBrowserContext());
734  PrefService* prefs = profile->GetPrefs();
735
736  // Create the new infobar to display.
737  TranslateInfoBarDelegate* infobar;
738  if (details->error_type != TranslateErrors::NONE) {
739    infobar = TranslateInfoBarDelegate::CreateErrorDelegate(
740        details->error_type,
741        infobar_helper,
742        prefs,
743        details->source_language,
744        details->target_language);
745  } else if (!IsSupportedLanguage(details->source_language)) {
746    // TODO(jcivelli): http://crbug.com/9390 We should change the "after
747    //                 translate" infobar to support unknown as the original
748    //                 language.
749    UMA_HISTOGRAM_COUNTS("Translate.ServerReportedUnsupportedLanguage", 1);
750    infobar = TranslateInfoBarDelegate::CreateErrorDelegate(
751        TranslateErrors::UNSUPPORTED_LANGUAGE, infobar_helper,
752        prefs, details->source_language, details->target_language);
753  } else {
754    infobar = TranslateInfoBarDelegate::CreateDelegate(
755        TranslateInfoBarDelegate::AFTER_TRANSLATE, infobar_helper,
756        prefs, details->source_language, details->target_language);
757  }
758  ShowInfoBar(web_contents, infobar);
759}
760
761bool TranslateManager::IsAcceptLanguage(WebContents* web_contents,
762                                        const std::string& language) {
763  Profile* profile =
764      Profile::FromBrowserContext(web_contents->GetBrowserContext());
765  profile = profile->GetOriginalProfile();
766  PrefService* pref_service = profile->GetPrefs();
767  PrefServiceLanguagesMap::const_iterator iter =
768      accept_languages_.find(pref_service);
769  if (iter == accept_languages_.end()) {
770    InitAcceptLanguages(pref_service);
771    // Listen for this profile going away, in which case we would need to clear
772    // the accepted languages for the profile.
773    notification_registrar_.Add(this, chrome::NOTIFICATION_PROFILE_DESTROYED,
774                                content::Source<Profile>(profile));
775    // Also start listening for changes in the accept languages.
776    DCHECK(pref_change_registrars_.find(pref_service) ==
777           pref_change_registrars_.end());
778    PrefChangeRegistrar* pref_change_registrar = new PrefChangeRegistrar;
779    pref_change_registrar->Init(pref_service);
780    pref_change_registrar->Add(prefs::kAcceptLanguages, this);
781    pref_change_registrars_[pref_service] = pref_change_registrar;
782
783    iter = accept_languages_.find(pref_service);
784  }
785
786  return iter->second.count(language) != 0;
787}
788
789void TranslateManager::InitAcceptLanguages(PrefServiceBase* prefs) {
790  // We have been asked for this profile, build the languages.
791  std::string accept_langs_str = prefs->GetString(prefs::kAcceptLanguages);
792  std::vector<std::string> accept_langs_list;
793  LanguageSet accept_langs_set;
794  base::SplitString(accept_langs_str, ',', &accept_langs_list);
795  std::vector<std::string>::const_iterator iter;
796  std::string ui_lang =
797      GetLanguageCode(g_browser_process->GetApplicationLocale());
798  bool is_ui_english = StartsWithASCII(ui_lang, "en-", false);
799  for (iter = accept_langs_list.begin();
800       iter != accept_langs_list.end(); ++iter) {
801    // Get rid of the locale extension if any (ex: en-US -> en), but for Chinese
802    // for which the CLD reports zh-CN and zh-TW.
803    std::string accept_lang(*iter);
804    size_t index = iter->find("-");
805    if (index != std::string::npos && *iter != "zh-CN" && *iter != "zh-TW")
806      accept_lang = iter->substr(0, index);
807    // Special-case English until we resolve bug 36182 properly.
808    // Add English only if the UI language is not English. This will annoy
809    // users of non-English Chrome who can comprehend English until English is
810    // black-listed.
811    // TODO(jungshik): Once we determine that it's safe to remove English from
812    // the default Accept-Language values for most locales, remove this
813    // special-casing.
814    if (accept_lang != "en" || is_ui_english)
815      accept_langs_set.insert(accept_lang);
816  }
817  accept_languages_[prefs] = accept_langs_set;
818}
819
820void TranslateManager::FetchLanguageListFromTranslateServer(
821    PrefService* prefs) {
822  if (language_list_request_pending_.get() != NULL)
823    return;
824
825  // We don't want to do this when translate is disabled.
826  DCHECK(prefs != NULL);
827  if (CommandLine::ForCurrentProcess()->HasSwitch(
828      switches::kDisableTranslate) ||
829      (prefs != NULL && !prefs->GetBoolean(prefs::kEnableTranslate))) {
830    return;
831  }
832
833  GURL language_list_fetch_url = GURL(
834      base::StringPrintf(
835          kLanguageListFetchURL,
836          GetLanguageCode(g_browser_process->GetApplicationLocale()).c_str()));
837  AddApiKeyToUrl(&language_list_fetch_url);
838  language_list_request_pending_.reset(net::URLFetcher::Create(
839      1, language_list_fetch_url, net::URLFetcher::GET, this));
840  language_list_request_pending_->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES |
841                                               net::LOAD_DO_NOT_SAVE_COOKIES);
842  language_list_request_pending_->SetRequestContext(
843      g_browser_process->system_request_context());
844  language_list_request_pending_->SetMaxRetries(kMaxRetryLanguageListFetch);
845  language_list_request_pending_->Start();
846}
847
848void TranslateManager::CleanupPendingUlrFetcher() {
849  language_list_request_pending_.reset();
850  translate_script_request_pending_.reset();
851}
852
853void TranslateManager::RequestTranslateScript() {
854  if (translate_script_request_pending_.get() != NULL)
855    return;
856
857  GURL translate_script_url = GURL(base::StringPrintf(
858      kTranslateScriptURL,
859      GetLanguageCode(g_browser_process->GetApplicationLocale()).c_str()));
860  AddApiKeyToUrl(&translate_script_url);
861  translate_script_request_pending_.reset(net::URLFetcher::Create(
862      0, translate_script_url, net::URLFetcher::GET, this));
863  translate_script_request_pending_->SetLoadFlags(
864      net::LOAD_DO_NOT_SEND_COOKIES | net::LOAD_DO_NOT_SAVE_COOKIES);
865  translate_script_request_pending_->SetRequestContext(
866      g_browser_process->system_request_context());
867  translate_script_request_pending_->SetExtraRequestHeaders(
868      kTranslateScriptHeader);
869  translate_script_request_pending_->Start();
870}
871
872void TranslateManager::ShowInfoBar(content::WebContents* web_contents,
873                                   TranslateInfoBarDelegate* infobar) {
874  DCHECK(infobar != NULL);
875  TranslateInfoBarDelegate* old_infobar =
876      GetTranslateInfoBarDelegate(web_contents);
877  infobar->UpdateBackgroundAnimation(old_infobar);
878  InfoBarTabHelper* infobar_helper =
879      InfoBarTabHelper::FromWebContents(web_contents);
880  if (!infobar_helper)
881    return;
882  if (old_infobar) {
883    // There already is a translate infobar, simply replace it.
884    infobar_helper->ReplaceInfoBar(old_infobar, infobar);
885  } else {
886    infobar_helper->AddInfoBar(infobar);
887  }
888}
889
890// static
891std::string TranslateManager::GetTargetLanguage(PrefService* prefs) {
892  std::string ui_lang =
893    GetLanguageCode(g_browser_process->GetApplicationLocale());
894  if (IsSupportedLanguage(ui_lang))
895    return ui_lang;
896
897  // Getting the accepted languages list
898  std::string accept_langs_str = prefs->GetString(prefs::kAcceptLanguages);
899
900  std::vector<std::string> accept_langs_list;
901  base::SplitString(accept_langs_str, ',', &accept_langs_list);
902
903  // Will translate to the first supported language on the Accepted Language
904  // list or not at all if no such candidate exists
905  std::vector<std::string>::iterator iter;
906  for (iter = accept_langs_list.begin();
907       iter != accept_langs_list.end(); ++iter) {
908    std::string lang_code = GetLanguageCode(*iter);
909    if (IsSupportedLanguage(lang_code))
910      return lang_code;
911  }
912  return std::string();
913}
914
915// static
916TranslateInfoBarDelegate* TranslateManager::GetTranslateInfoBarDelegate(
917    WebContents* web_contents) {
918  InfoBarTabHelper* infobar_helper =
919      InfoBarTabHelper::FromWebContents(web_contents);
920  if (!infobar_helper)
921    return NULL;
922
923  for (size_t i = 0; i < infobar_helper->GetInfoBarCount(); ++i) {
924    TranslateInfoBarDelegate* delegate =
925        infobar_helper->GetInfoBarDelegateAt(i)->
926            AsTranslateInfoBarDelegate();
927    if (delegate)
928      return delegate;
929  }
930  return NULL;
931}
932