chrome_translate_client.cc revision 5f1c94371a64b3196d4be9466099bb892df9b88e
1// Copyright 2014 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/chrome_translate_client.h" 6 7#include <vector> 8 9#include "base/logging.h" 10#include "base/path_service.h" 11#include "base/prefs/pref_service.h" 12#include "base/strings/string_split.h" 13#include "chrome/browser/chrome_notification_types.h" 14#include "chrome/browser/infobars/infobar_service.h" 15#include "chrome/browser/profiles/profile.h" 16#include "chrome/browser/translate/translate_accept_languages_factory.h" 17#include "chrome/browser/translate/translate_service.h" 18#include "chrome/browser/ui/browser.h" 19#include "chrome/browser/ui/browser_finder.h" 20#include "chrome/browser/ui/browser_tabstrip.h" 21#include "chrome/browser/ui/browser_window.h" 22#include "chrome/browser/ui/tabs/tab_strip_model.h" 23#include "chrome/browser/ui/translate/translate_bubble_factory.h" 24#include "chrome/common/chrome_paths.h" 25#include "chrome/common/pref_names.h" 26#include "components/infobars/core/infobar.h" 27#include "components/translate/content/common/cld_data_source.h" 28#include "components/translate/content/common/translate_messages.h" 29#include "components/translate/core/browser/language_state.h" 30#include "components/translate/core/browser/page_translated_details.h" 31#include "components/translate/core/browser/translate_accept_languages.h" 32#include "components/translate/core/browser/translate_download_manager.h" 33#include "components/translate/core/browser/translate_infobar_delegate.h" 34#include "components/translate/core/browser/translate_manager.h" 35#include "components/translate/core/browser/translate_prefs.h" 36#include "components/translate/core/common/language_detection_details.h" 37#include "content/public/browser/navigation_details.h" 38#include "content/public/browser/navigation_entry.h" 39#include "content/public/browser/notification_service.h" 40#include "content/public/browser/render_view_host.h" 41#include "content/public/browser/web_contents.h" 42#include "grit/theme_resources.h" 43#include "net/http/http_status_code.h" 44#include "url/gurl.h" 45 46namespace { 47 48// The maximum number of attempts we'll do to see if the page has finshed 49// loading before giving up the translation 50const int kMaxTranslateLoadCheckAttempts = 20; 51 52// TODO(andrewhayden): Make the data file path into a gyp/gn define 53// If you change this, also update standalone_cld_data_harness.cc 54// accordingly! 55const base::FilePath::CharType kCldDataFileName[] = 56 FILE_PATH_LITERAL("cld2_data.bin"); 57 58bool g_cld_file_path_initialized_ = false; 59 60} // namespace 61 62DEFINE_WEB_CONTENTS_USER_DATA_KEY(ChromeTranslateClient); 63 64ChromeTranslateClient::ChromeTranslateClient(content::WebContents* web_contents) 65 : content::WebContentsObserver(web_contents), 66 max_reload_check_attempts_(kMaxTranslateLoadCheckAttempts), 67 translate_driver_(&web_contents->GetController()), 68 translate_manager_( 69 new translate::TranslateManager(this, prefs::kAcceptLanguages)), 70 cld_data_provider_( 71 translate::CreateBrowserCldDataProviderFor(web_contents)), 72 weak_pointer_factory_(this) { 73 // Customization: for the standalone data source, we configure the path to 74 // CLD data immediately on startup. 75 if (translate::CldDataSource::ShouldUseStandaloneDataFile() && 76 !g_cld_file_path_initialized_) { 77 VLOG(1) << "Initializing CLD file path for the first time."; 78 base::FilePath path; 79 if (!PathService::Get(chrome::DIR_USER_DATA, &path)) { 80 // Chrome isn't properly installed 81 LOG(WARNING) << "Unable to locate user data directory"; 82 } else { 83 g_cld_file_path_initialized_ = true; 84 path = path.Append(kCldDataFileName); 85 VLOG(1) << "Setting CLD data file path: " << path.value(); 86 translate::SetCldDataFilePath(path); 87 } 88 } 89} 90 91ChromeTranslateClient::~ChromeTranslateClient() { 92} 93 94translate::LanguageState& ChromeTranslateClient::GetLanguageState() { 95 return translate_manager_->GetLanguageState(); 96} 97 98// static 99scoped_ptr<translate::TranslatePrefs> 100ChromeTranslateClient::CreateTranslatePrefs(PrefService* prefs) { 101#if defined(OS_CHROMEOS) 102 const char* preferred_languages_prefs = prefs::kLanguagePreferredLanguages; 103#else 104 const char* preferred_languages_prefs = NULL; 105#endif 106 return scoped_ptr<translate::TranslatePrefs>(new translate::TranslatePrefs( 107 prefs, prefs::kAcceptLanguages, preferred_languages_prefs)); 108} 109 110// static 111translate::TranslateAcceptLanguages* 112ChromeTranslateClient::GetTranslateAcceptLanguages( 113 content::BrowserContext* browser_context) { 114 return TranslateAcceptLanguagesFactory::GetForBrowserContext(browser_context); 115} 116 117// static 118translate::TranslateManager* ChromeTranslateClient::GetManagerFromWebContents( 119 content::WebContents* web_contents) { 120 ChromeTranslateClient* chrome_translate_client = 121 FromWebContents(web_contents); 122 if (!chrome_translate_client) 123 return NULL; 124 return chrome_translate_client->GetTranslateManager(); 125} 126 127// static 128void ChromeTranslateClient::GetTranslateLanguages( 129 content::WebContents* web_contents, 130 std::string* source, 131 std::string* target) { 132 DCHECK(source != NULL); 133 DCHECK(target != NULL); 134 135 ChromeTranslateClient* chrome_translate_client = 136 FromWebContents(web_contents); 137 if (!chrome_translate_client) 138 return; 139 140 *source = translate::TranslateDownloadManager::GetLanguageCode( 141 chrome_translate_client->GetLanguageState().original_language()); 142 143 Profile* profile = 144 Profile::FromBrowserContext(web_contents->GetBrowserContext()); 145 Profile* original_profile = profile->GetOriginalProfile(); 146 PrefService* prefs = original_profile->GetPrefs(); 147 scoped_ptr<translate::TranslatePrefs> translate_prefs = 148 CreateTranslatePrefs(prefs); 149 if (!web_contents->GetBrowserContext()->IsOffTheRecord()) { 150 std::string auto_translate_language = 151 translate::TranslateManager::GetAutoTargetLanguage( 152 *source, translate_prefs.get()); 153 if (!auto_translate_language.empty()) { 154 *target = auto_translate_language; 155 return; 156 } 157 } 158 159 std::string accept_languages_str = prefs->GetString(prefs::kAcceptLanguages); 160 std::vector<std::string> accept_languages_list; 161 base::SplitString(accept_languages_str, ',', &accept_languages_list); 162 *target = 163 translate::TranslateManager::GetTargetLanguage(accept_languages_list); 164} 165 166translate::TranslateManager* ChromeTranslateClient::GetTranslateManager() { 167 return translate_manager_.get(); 168} 169 170content::WebContents* ChromeTranslateClient::GetWebContents() { 171 return web_contents(); 172} 173 174void ChromeTranslateClient::ShowTranslateUI( 175 translate::TranslateStep step, 176 const std::string source_language, 177 const std::string target_language, 178 translate::TranslateErrors::Type error_type, 179 bool triggered_from_menu) { 180 DCHECK(web_contents()); 181 if (error_type != translate::TranslateErrors::NONE) 182 step = translate::TRANSLATE_STEP_TRANSLATE_ERROR; 183 184 if (TranslateService::IsTranslateBubbleEnabled()) { 185 // Bubble UI. 186 if (step == translate::TRANSLATE_STEP_BEFORE_TRANSLATE) { 187 // TODO(droger): Move this logic out of UI code. 188 GetLanguageState().SetTranslateEnabled(true); 189 if (!GetLanguageState().HasLanguageChanged()) 190 return; 191 192 if (!triggered_from_menu) { 193 if (web_contents()->GetBrowserContext()->IsOffTheRecord()) 194 return; 195 if (GetTranslatePrefs()->IsTooOftenDenied()) 196 return; 197 } 198 } 199 ShowBubble(step, error_type); 200 return; 201 } 202 203 // Infobar UI. 204 translate::TranslateInfoBarDelegate::Create( 205 step != translate::TRANSLATE_STEP_BEFORE_TRANSLATE, 206 translate_manager_->GetWeakPtr(), 207 InfoBarService::FromWebContents(web_contents()), 208 web_contents()->GetBrowserContext()->IsOffTheRecord(), 209 step, 210 source_language, 211 target_language, 212 error_type, 213 triggered_from_menu); 214} 215 216translate::TranslateDriver* ChromeTranslateClient::GetTranslateDriver() { 217 return &translate_driver_; 218} 219 220PrefService* ChromeTranslateClient::GetPrefs() { 221 DCHECK(web_contents()); 222 Profile* profile = 223 Profile::FromBrowserContext(web_contents()->GetBrowserContext()); 224 return profile->GetOriginalProfile()->GetPrefs(); 225} 226 227scoped_ptr<translate::TranslatePrefs> 228ChromeTranslateClient::GetTranslatePrefs() { 229 DCHECK(web_contents()); 230 Profile* profile = 231 Profile::FromBrowserContext(web_contents()->GetBrowserContext()); 232 return CreateTranslatePrefs(profile->GetPrefs()); 233} 234 235translate::TranslateAcceptLanguages* 236ChromeTranslateClient::GetTranslateAcceptLanguages() { 237 DCHECK(web_contents()); 238 return GetTranslateAcceptLanguages(web_contents()->GetBrowserContext()); 239} 240 241int ChromeTranslateClient::GetInfobarIconID() const { 242 return IDR_INFOBAR_TRANSLATE; 243} 244 245// ChromeTranslateClient::CreateInfoBar() is implemented in platform-specific 246// files, except the TOOLKIT_VIEWS implementation, which has been removed. Note 247// for Mac, Cocoa is still providing the infobar in a toolkit-views build. 248#if defined(TOOLKIT_VIEWS) && !defined(OS_MACOSX) 249scoped_ptr<infobars::InfoBar> ChromeTranslateClient::CreateInfoBar( 250 scoped_ptr<translate::TranslateInfoBarDelegate> delegate) const { 251 return scoped_ptr<infobars::InfoBar>(); 252} 253#endif 254 255bool ChromeTranslateClient::IsTranslatableURL(const GURL& url) { 256 return TranslateService::IsTranslatableURL(url); 257} 258 259void ChromeTranslateClient::ShowReportLanguageDetectionErrorUI( 260 const GURL& report_url) { 261#if defined(OS_ANDROID) 262 // Android does not support reporting language detection errors. 263 NOTREACHED(); 264#else 265 // We'll open the URL in a new tab so that the user can tell us more. 266 Browser* browser = chrome::FindBrowserWithWebContents(web_contents()); 267 if (!browser) { 268 NOTREACHED(); 269 return; 270 } 271 272 chrome::AddSelectedTabWithURL( 273 browser, report_url, content::PAGE_TRANSITION_AUTO_BOOKMARK); 274#endif // defined(OS_ANDROID) 275} 276 277bool ChromeTranslateClient::OnMessageReceived(const IPC::Message& message) { 278 bool handled = true; 279 IPC_BEGIN_MESSAGE_MAP(ChromeTranslateClient, message) 280 IPC_MESSAGE_HANDLER(ChromeViewHostMsg_TranslateAssignedSequenceNumber, 281 OnTranslateAssignedSequenceNumber) 282 IPC_MESSAGE_HANDLER(ChromeViewHostMsg_TranslateLanguageDetermined, 283 OnLanguageDetermined) 284 IPC_MESSAGE_HANDLER(ChromeViewHostMsg_PageTranslated, OnPageTranslated) 285 IPC_MESSAGE_UNHANDLED(handled = false) 286 IPC_END_MESSAGE_MAP() 287 288 if (!handled) { 289 handled = cld_data_provider_->OnMessageReceived(message); 290 } 291 return handled; 292} 293 294void ChromeTranslateClient::NavigationEntryCommitted( 295 const content::LoadCommittedDetails& load_details) { 296 // Check whether this is a reload: When doing a page reload, the 297 // TranslateLanguageDetermined IPC is not sent so the translation needs to be 298 // explicitly initiated. 299 300 content::NavigationEntry* entry = 301 web_contents()->GetController().GetActiveEntry(); 302 if (!entry) { 303 NOTREACHED(); 304 return; 305 } 306 307 // If the navigation happened while offline don't show the translate 308 // bar since there will be nothing to translate. 309 if (load_details.http_status_code == 0 || 310 load_details.http_status_code == net::HTTP_INTERNAL_SERVER_ERROR) { 311 return; 312 } 313 314 if (!load_details.is_main_frame && 315 GetLanguageState().translation_declined()) { 316 // Some sites (such as Google map) may trigger sub-frame navigations 317 // when the user interacts with the page. We don't want to show a new 318 // infobar if the user already dismissed one in that case. 319 return; 320 } 321 322 // If not a reload, return. 323 if (entry->GetTransitionType() != content::PAGE_TRANSITION_RELOAD && 324 load_details.type != content::NAVIGATION_TYPE_SAME_PAGE) { 325 return; 326 } 327 328 if (!GetLanguageState().page_needs_translation()) 329 return; 330 331 // Note that we delay it as the ordering of the processing of this callback 332 // by WebContentsObservers is undefined and might result in the current 333 // infobars being removed. Since the translation initiation process might add 334 // an infobar, it must be done after that. 335 base::MessageLoop::current()->PostTask( 336 FROM_HERE, 337 base::Bind(&ChromeTranslateClient::InitiateTranslation, 338 weak_pointer_factory_.GetWeakPtr(), 339 GetLanguageState().original_language(), 340 0)); 341} 342 343void ChromeTranslateClient::DidNavigateAnyFrame( 344 const content::LoadCommittedDetails& details, 345 const content::FrameNavigateParams& params) { 346 // Let the LanguageState clear its state. 347 const bool reload = 348 details.entry->GetTransitionType() == content::PAGE_TRANSITION_RELOAD || 349 details.type == content::NAVIGATION_TYPE_SAME_PAGE; 350 GetLanguageState().DidNavigate( 351 details.is_in_page, details.is_main_frame, reload); 352} 353 354void ChromeTranslateClient::WebContentsDestroyed() { 355 // Translation process can be interrupted. 356 // Destroying the TranslateManager now guarantees that it never has to deal 357 // with NULL WebContents. 358 translate_manager_.reset(); 359} 360 361void ChromeTranslateClient::InitiateTranslation(const std::string& page_lang, 362 int attempt) { 363 if (GetLanguageState().translation_pending()) 364 return; 365 366 // During a reload we need web content to be available before the 367 // translate script is executed. Otherwise we will run the translate script on 368 // an empty DOM which will fail. Therefore we wait a bit to see if the page 369 // has finished. 370 if (web_contents()->IsLoading() && attempt < max_reload_check_attempts_) { 371 int backoff = attempt * kMaxTranslateLoadCheckAttempts; 372 base::MessageLoop::current()->PostDelayedTask( 373 FROM_HERE, 374 base::Bind(&ChromeTranslateClient::InitiateTranslation, 375 weak_pointer_factory_.GetWeakPtr(), 376 page_lang, 377 attempt + 1), 378 base::TimeDelta::FromMilliseconds(backoff)); 379 return; 380 } 381 382 translate_manager_->InitiateTranslation( 383 translate::TranslateDownloadManager::GetLanguageCode(page_lang)); 384} 385 386void ChromeTranslateClient::OnTranslateAssignedSequenceNumber(int page_seq_no) { 387 translate_manager_->set_current_seq_no(page_seq_no); 388} 389 390void ChromeTranslateClient::OnLanguageDetermined( 391 const translate::LanguageDetectionDetails& details, 392 bool page_needs_translation) { 393 GetLanguageState().LanguageDetermined(details.adopted_language, 394 page_needs_translation); 395 396 if (web_contents()) 397 translate_manager_->InitiateTranslation(details.adopted_language); 398 399 content::NotificationService::current()->Notify( 400 chrome::NOTIFICATION_TAB_LANGUAGE_DETERMINED, 401 content::Source<content::WebContents>(web_contents()), 402 content::Details<const translate::LanguageDetectionDetails>(&details)); 403} 404 405void ChromeTranslateClient::OnPageTranslated( 406 const std::string& original_lang, 407 const std::string& translated_lang, 408 translate::TranslateErrors::Type error_type) { 409 DCHECK(web_contents()); 410 translate_manager_->PageTranslated( 411 original_lang, translated_lang, error_type); 412 413 translate::PageTranslatedDetails details; 414 details.source_language = original_lang; 415 details.target_language = translated_lang; 416 details.error_type = error_type; 417 content::NotificationService::current()->Notify( 418 chrome::NOTIFICATION_PAGE_TRANSLATED, 419 content::Source<content::WebContents>(web_contents()), 420 content::Details<translate::PageTranslatedDetails>(&details)); 421} 422 423void ChromeTranslateClient::ShowBubble( 424 translate::TranslateStep step, 425 translate::TranslateErrors::Type error_type) { 426// The bubble is implemented only on the desktop platforms. 427#if !defined(OS_ANDROID) && !defined(OS_IOS) 428 Browser* browser = chrome::FindBrowserWithWebContents(web_contents()); 429 430 // |browser| might be NULL when testing. In this case, Show(...) should be 431 // called because the implementation for testing is used. 432 if (!browser) { 433 TranslateBubbleFactory::Show(NULL, web_contents(), step, error_type); 434 return; 435 } 436 437 if (web_contents() != browser->tab_strip_model()->GetActiveWebContents()) 438 return; 439 440 // This ShowBubble function is also used for upating the existing bubble. 441 // However, with the bubble shown, any browser windows are NOT activated 442 // because the bubble takes the focus from the other widgets including the 443 // browser windows. So it is checked that |browser| is the last activated 444 // browser, not is now activated. 445 if (browser != 446 chrome::FindLastActiveWithHostDesktopType(browser->host_desktop_type())) { 447 return; 448 } 449 450 // During auto-translating, the bubble should not be shown. 451 if (step == translate::TRANSLATE_STEP_TRANSLATING || 452 step == translate::TRANSLATE_STEP_AFTER_TRANSLATE) { 453 if (GetLanguageState().InTranslateNavigation()) 454 return; 455 } 456 457 TranslateBubbleFactory::Show( 458 browser->window(), web_contents(), step, error_type); 459#else 460 NOTREACHED(); 461#endif 462} 463