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 "components/translate/core/browser/translate_manager.h" 6 7 #include "base/bind.h" 8 #include "base/command_line.h" 9 #include "base/metrics/field_trial.h" 10 #include "base/metrics/histogram.h" 11 #include "base/prefs/pref_service.h" 12 #include "base/strings/string_split.h" 13 #include "base/strings/stringprintf.h" 14 #include "base/time/time.h" 15 #include "components/translate/core/browser/language_state.h" 16 #include "components/translate/core/browser/page_translated_details.h" 17 #include "components/translate/core/browser/translate_accept_languages.h" 18 #include "components/translate/core/browser/translate_browser_metrics.h" 19 #include "components/translate/core/browser/translate_client.h" 20 #include "components/translate/core/browser/translate_download_manager.h" 21 #include "components/translate/core/browser/translate_driver.h" 22 #include "components/translate/core/browser/translate_error_details.h" 23 #include "components/translate/core/browser/translate_language_list.h" 24 #include "components/translate/core/browser/translate_prefs.h" 25 #include "components/translate/core/browser/translate_script.h" 26 #include "components/translate/core/browser/translate_url_util.h" 27 #include "components/translate/core/common/language_detection_details.h" 28 #include "components/translate/core/common/translate_constants.h" 29 #include "components/translate/core/common/translate_pref_names.h" 30 #include "components/translate/core/common/translate_switches.h" 31 #include "net/base/url_util.h" 32 #include "net/http/http_status_code.h" 33 34 namespace translate { 35 36 namespace { 37 38 // Callbacks for translate errors. 39 TranslateManager::TranslateErrorCallbackList* g_callback_list_ = NULL; 40 41 const char kReportLanguageDetectionErrorURL[] = 42 "https://translate.google.com/translate_error?client=cr&action=langidc"; 43 44 // Used in kReportLanguageDetectionErrorURL to specify the original page 45 // language. 46 const char kSourceLanguageQueryName[] = "sl"; 47 48 // Used in kReportLanguageDetectionErrorURL to specify the page URL. 49 const char kUrlQueryName[] = "u"; 50 51 // Notifies |g_callback_list_| of translate errors. 52 void NotifyTranslateError(const TranslateErrorDetails& details) { 53 if (!g_callback_list_) 54 return; 55 56 g_callback_list_->Notify(details); 57 } 58 59 } // namespace 60 61 TranslateManager::~TranslateManager() {} 62 63 // static 64 scoped_ptr<TranslateManager::TranslateErrorCallbackList::Subscription> 65 TranslateManager::RegisterTranslateErrorCallback( 66 const TranslateManager::TranslateErrorCallback& callback) { 67 if (!g_callback_list_) 68 g_callback_list_ = new TranslateErrorCallbackList; 69 return g_callback_list_->Add(callback); 70 } 71 72 TranslateManager::TranslateManager( 73 TranslateClient* translate_client, 74 const std::string& accept_languages_pref_name) 75 : page_seq_no_(0), 76 accept_languages_pref_name_(accept_languages_pref_name), 77 translate_client_(translate_client), 78 translate_driver_(translate_client_->GetTranslateDriver()), 79 language_state_(translate_driver_), 80 weak_method_factory_(this) { 81 } 82 83 base::WeakPtr<TranslateManager> TranslateManager::GetWeakPtr() { 84 return weak_method_factory_.GetWeakPtr(); 85 } 86 87 void TranslateManager::InitiateTranslation(const std::string& page_lang) { 88 // Short-circuit out if not in a state where initiating translation makes 89 // sense (this method may be called muhtiple times for a given page). 90 if (!language_state_.page_needs_translation() || 91 language_state_.translation_pending() || 92 language_state_.translation_declined() || 93 language_state_.IsPageTranslated()) { 94 return; 95 } 96 97 PrefService* prefs = translate_client_->GetPrefs(); 98 if (!prefs->GetBoolean(prefs::kEnableTranslate)) { 99 TranslateBrowserMetrics::ReportInitiationStatus( 100 TranslateBrowserMetrics::INITIATION_STATUS_DISABLED_BY_PREFS); 101 const std::string& locale = 102 TranslateDownloadManager::GetInstance()->application_locale(); 103 TranslateBrowserMetrics::ReportLocalesOnDisabledByPrefs(locale); 104 return; 105 } 106 107 // Allow disabling of translate from the command line to assist with 108 // automated browser testing. 109 if (CommandLine::ForCurrentProcess()->HasSwitch( 110 translate::switches::kDisableTranslate)) { 111 TranslateBrowserMetrics::ReportInitiationStatus( 112 TranslateBrowserMetrics::INITIATION_STATUS_DISABLED_BY_SWITCH); 113 return; 114 } 115 116 // MHTML pages currently cannot be translated. 117 // See bug: 217945. 118 if (translate_driver_->GetContentsMimeType() == "multipart/related") { 119 TranslateBrowserMetrics::ReportInitiationStatus( 120 TranslateBrowserMetrics::INITIATION_STATUS_MIME_TYPE_IS_NOT_SUPPORTED); 121 return; 122 } 123 124 // Don't translate any Chrome specific page, e.g., New Tab Page, Download, 125 // History, and so on. 126 const GURL& page_url = translate_driver_->GetVisibleURL(); 127 if (!translate_client_->IsTranslatableURL(page_url)) { 128 TranslateBrowserMetrics::ReportInitiationStatus( 129 TranslateBrowserMetrics::INITIATION_STATUS_URL_IS_NOT_SUPPORTED); 130 return; 131 } 132 133 // Get the accepted languages list. 134 std::vector<std::string> accept_languages_list; 135 base::SplitString(prefs->GetString(accept_languages_pref_name_.c_str()), ',', 136 &accept_languages_list); 137 138 std::string target_lang = GetTargetLanguage(accept_languages_list); 139 std::string language_code = 140 TranslateDownloadManager::GetLanguageCode(page_lang); 141 142 // Don't translate similar languages (ex: en-US to en). 143 if (language_code == target_lang) { 144 TranslateBrowserMetrics::ReportInitiationStatus( 145 TranslateBrowserMetrics::INITIATION_STATUS_SIMILAR_LANGUAGES); 146 return; 147 } 148 149 // Nothing to do if either the language Chrome is in or the language of the 150 // page is not supported by the translation server. 151 if (target_lang.empty() || 152 !TranslateDownloadManager::IsSupportedLanguage(language_code)) { 153 TranslateBrowserMetrics::ReportInitiationStatus( 154 TranslateBrowserMetrics::INITIATION_STATUS_LANGUAGE_IS_NOT_SUPPORTED); 155 TranslateBrowserMetrics::ReportUnsupportedLanguageAtInitiation( 156 language_code); 157 return; 158 } 159 160 scoped_ptr<TranslatePrefs> translate_prefs( 161 translate_client_->GetTranslatePrefs()); 162 163 TranslateAcceptLanguages* accept_languages = 164 translate_client_->GetTranslateAcceptLanguages(); 165 // Don't translate any user black-listed languages. 166 if (!translate_prefs->CanTranslateLanguage(accept_languages, 167 language_code)) { 168 TranslateBrowserMetrics::ReportInitiationStatus( 169 TranslateBrowserMetrics::INITIATION_STATUS_DISABLED_BY_CONFIG); 170 return; 171 } 172 173 // Don't translate any user black-listed URLs. 174 if (translate_prefs->IsSiteBlacklisted(page_url.HostNoBrackets())) { 175 TranslateBrowserMetrics::ReportInitiationStatus( 176 TranslateBrowserMetrics::INITIATION_STATUS_DISABLED_BY_CONFIG); 177 return; 178 } 179 180 // If the user has previously selected "always translate" for this language we 181 // automatically translate. Note that in incognito mode we disable that 182 // feature; the user will get an infobar, so they can control whether the 183 // page's text is sent to the translate server. 184 if (!translate_driver_->IsOffTheRecord()) { 185 scoped_ptr<TranslatePrefs> translate_prefs = 186 translate_client_->GetTranslatePrefs(); 187 std::string auto_target_lang = 188 GetAutoTargetLanguage(language_code, translate_prefs.get()); 189 if (!auto_target_lang.empty()) { 190 TranslateBrowserMetrics::ReportInitiationStatus( 191 TranslateBrowserMetrics::INITIATION_STATUS_AUTO_BY_CONFIG); 192 TranslatePage(language_code, auto_target_lang, false); 193 return; 194 } 195 } 196 197 std::string auto_translate_to = language_state_.AutoTranslateTo(); 198 if (!auto_translate_to.empty()) { 199 // This page was navigated through a click from a translated page. 200 TranslateBrowserMetrics::ReportInitiationStatus( 201 TranslateBrowserMetrics::INITIATION_STATUS_AUTO_BY_LINK); 202 TranslatePage(language_code, auto_translate_to, false); 203 return; 204 } 205 206 TranslateBrowserMetrics::ReportInitiationStatus( 207 TranslateBrowserMetrics::INITIATION_STATUS_SHOW_INFOBAR); 208 209 // Prompts the user if he/she wants the page translated. 210 translate_client_->ShowTranslateUI(translate::TRANSLATE_STEP_BEFORE_TRANSLATE, 211 language_code, 212 target_lang, 213 TranslateErrors::NONE, 214 false); 215 } 216 217 void TranslateManager::TranslatePage(const std::string& original_source_lang, 218 const std::string& target_lang, 219 bool triggered_from_menu) { 220 if (!translate_driver_->HasCurrentPage()) { 221 NOTREACHED(); 222 return; 223 } 224 225 // Translation can be kicked by context menu against unsupported languages. 226 // Unsupported language strings should be replaced with 227 // kUnknownLanguageCode in order to send a translation request with enabling 228 // server side auto language detection. 229 std::string source_lang(original_source_lang); 230 if (!TranslateDownloadManager::IsSupportedLanguage(source_lang)) 231 source_lang = std::string(translate::kUnknownLanguageCode); 232 233 translate_client_->ShowTranslateUI(translate::TRANSLATE_STEP_TRANSLATING, 234 source_lang, 235 target_lang, 236 TranslateErrors::NONE, 237 triggered_from_menu); 238 239 TranslateScript* script = TranslateDownloadManager::GetInstance()->script(); 240 DCHECK(script != NULL); 241 242 const std::string& script_data = script->data(); 243 if (!script_data.empty()) { 244 DoTranslatePage(script_data, source_lang, target_lang); 245 return; 246 } 247 248 // The script is not available yet. Queue that request and query for the 249 // script. Once it is downloaded we'll do the translate. 250 TranslateScript::RequestCallback callback = base::Bind( 251 &TranslateManager::OnTranslateScriptFetchComplete, GetWeakPtr(), 252 source_lang, target_lang); 253 254 script->Request(callback); 255 } 256 257 void TranslateManager::RevertTranslation() { 258 translate_driver_->RevertTranslation(page_seq_no_); 259 language_state_.SetCurrentLanguage(language_state_.original_language()); 260 } 261 262 void TranslateManager::ReportLanguageDetectionError() { 263 TranslateBrowserMetrics::ReportLanguageDetectionError(); 264 265 GURL report_error_url = GURL(kReportLanguageDetectionErrorURL); 266 267 report_error_url = 268 net::AppendQueryParameter(report_error_url, 269 kUrlQueryName, 270 translate_driver_->GetActiveURL().spec()); 271 272 report_error_url = 273 net::AppendQueryParameter(report_error_url, 274 kSourceLanguageQueryName, 275 language_state_.original_language()); 276 277 report_error_url = translate::AddHostLocaleToUrl(report_error_url); 278 report_error_url = translate::AddApiKeyToUrl(report_error_url); 279 280 translate_client_->ShowReportLanguageDetectionErrorUI(report_error_url); 281 } 282 283 void TranslateManager::DoTranslatePage(const std::string& translate_script, 284 const std::string& source_lang, 285 const std::string& target_lang) { 286 language_state_.set_translation_pending(true); 287 translate_driver_->TranslatePage( 288 page_seq_no_, translate_script, source_lang, target_lang); 289 } 290 291 void TranslateManager::PageTranslated(const std::string& source_lang, 292 const std::string& target_lang, 293 TranslateErrors::Type error_type) { 294 language_state_.SetCurrentLanguage(target_lang); 295 language_state_.set_translation_pending(false); 296 297 if ((error_type == TranslateErrors::NONE) && 298 source_lang != translate::kUnknownLanguageCode && 299 !TranslateDownloadManager::IsSupportedLanguage(source_lang)) { 300 error_type = TranslateErrors::UNSUPPORTED_LANGUAGE; 301 } 302 303 translate_client_->ShowTranslateUI(translate::TRANSLATE_STEP_AFTER_TRANSLATE, 304 source_lang, 305 target_lang, 306 error_type, 307 false); 308 309 if (error_type != TranslateErrors::NONE && 310 !translate_driver_->IsOffTheRecord()) { 311 TranslateErrorDetails error_details; 312 error_details.time = base::Time::Now(); 313 error_details.url = translate_driver_->GetLastCommittedURL(); 314 error_details.error = error_type; 315 NotifyTranslateError(error_details); 316 } 317 } 318 319 void TranslateManager::OnTranslateScriptFetchComplete( 320 const std::string& source_lang, 321 const std::string& target_lang, 322 bool success, 323 const std::string& data) { 324 if (!translate_driver_->HasCurrentPage()) 325 return; 326 327 if (success) { 328 // Translate the page. 329 TranslateScript* translate_script = 330 TranslateDownloadManager::GetInstance()->script(); 331 DCHECK(translate_script); 332 DoTranslatePage(translate_script->data(), source_lang, target_lang); 333 } else { 334 translate_client_->ShowTranslateUI( 335 translate::TRANSLATE_STEP_TRANSLATE_ERROR, 336 source_lang, 337 target_lang, 338 TranslateErrors::NETWORK, 339 false); 340 if (!translate_driver_->IsOffTheRecord()) { 341 TranslateErrorDetails error_details; 342 error_details.time = base::Time::Now(); 343 error_details.url = translate_driver_->GetActiveURL(); 344 error_details.error = TranslateErrors::NETWORK; 345 NotifyTranslateError(error_details); 346 } 347 } 348 } 349 350 // static 351 std::string TranslateManager::GetTargetLanguage( 352 const std::vector<std::string>& accept_languages_list) { 353 std::string ui_lang = TranslatePrefs::ConvertLangCodeForTranslation( 354 TranslateDownloadManager::GetLanguageCode( 355 TranslateDownloadManager::GetInstance()->application_locale())); 356 357 if (TranslateDownloadManager::IsSupportedLanguage(ui_lang)) 358 return ui_lang; 359 360 // Will translate to the first supported language on the Accepted Language 361 // list or not at all if no such candidate exists 362 std::vector<std::string>::const_iterator iter; 363 for (iter = accept_languages_list.begin(); 364 iter != accept_languages_list.end(); ++iter) { 365 std::string lang_code = TranslateDownloadManager::GetLanguageCode(*iter); 366 if (TranslateDownloadManager::IsSupportedLanguage(lang_code)) 367 return lang_code; 368 } 369 return std::string(); 370 } 371 372 // static 373 std::string TranslateManager::GetAutoTargetLanguage( 374 const std::string& original_language, 375 TranslatePrefs* translate_prefs) { 376 std::string auto_target_lang; 377 if (translate_prefs->ShouldAutoTranslate(original_language, 378 &auto_target_lang)) { 379 // We need to confirm that the saved target language is still supported. 380 // Also, GetLanguageCode will take care of removing country code if any. 381 auto_target_lang = 382 TranslateDownloadManager::GetLanguageCode(auto_target_lang); 383 if (TranslateDownloadManager::IsSupportedLanguage(auto_target_lang)) 384 return auto_target_lang; 385 } 386 return std::string(); 387 } 388 389 LanguageState& TranslateManager::GetLanguageState() { 390 return language_state_; 391 } 392 393 } // namespace translate 394