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