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