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