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