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_infobar_delegate.h" 6 7 #include <algorithm> 8 9 #include "base/i18n/string_compare.h" 10 #include "base/metrics/histogram.h" 11 #include "base/prefs/pref_service.h" 12 #include "chrome/browser/browser_process.h" 13 #include "chrome/browser/infobars/infobar_service.h" 14 #include "chrome/browser/profiles/profile.h" 15 #include "chrome/browser/translate/translate_accept_languages.h" 16 #include "chrome/browser/translate/translate_manager.h" 17 #include "chrome/browser/translate/translate_tab_helper.h" 18 #include "content/public/browser/navigation_details.h" 19 #include "content/public/browser/navigation_entry.h" 20 #include "content/public/browser/web_contents.h" 21 #include "grit/generated_resources.h" 22 #include "grit/theme_resources.h" 23 #include "third_party/icu/source/i18n/unicode/coll.h" 24 #include "ui/base/l10n/l10n_util.h" 25 26 using content::NavigationEntry; 27 28 namespace { 29 30 const char kDeclineTranslate[] = "Translate.DeclineTranslate"; 31 const char kCloseInfobar[] = "Translate.DeclineTranslateCloseInfobar"; 32 const char kRevertTranslation[] = "Translate.RevertTranslation"; 33 const char kShowErrorInfobar[] = "Translate.ShowErrorInfobar"; 34 const char kPerformTranslate[] = "Translate.Translate"; 35 const char kNeverTranslateLang[] = "Translate.NeverTranslateLang"; 36 const char kNeverTranslateSite[] = "Translate.NeverTranslateSite"; 37 const char kAlwaysTranslateLang[] = "Translate.AlwaysTranslateLang"; 38 39 } // namespace 40 41 // static 42 const size_t TranslateInfoBarDelegate::kNoIndex = static_cast<size_t>(-1); 43 44 TranslateInfoBarDelegate::~TranslateInfoBarDelegate() { 45 } 46 47 // static 48 void TranslateInfoBarDelegate::Create( 49 bool replace_existing_infobar, 50 InfoBarService* infobar_service, 51 Type infobar_type, 52 const std::string& original_language, 53 const std::string& target_language, 54 TranslateErrors::Type error_type, 55 PrefService* prefs, 56 const ShortcutConfiguration& shortcut_config) { 57 // Check preconditions. 58 if (infobar_type != TRANSLATION_ERROR) { 59 DCHECK(TranslateManager::IsSupportedLanguage(target_language)); 60 if (!TranslateManager::IsSupportedLanguage(original_language)) { 61 // The original language can only be "unknown" for the "translating" 62 // infobar, which is the case when the user started a translation from the 63 // context menu. 64 DCHECK(infobar_type == TRANSLATING || infobar_type == AFTER_TRANSLATE); 65 DCHECK_EQ(chrome::kUnknownLanguageCode, original_language); 66 } 67 } 68 69 // Find any existing translate infobar delegate. 70 TranslateInfoBarDelegate* old_delegate = NULL; 71 for (size_t i = 0; i < infobar_service->infobar_count(); ++i) { 72 old_delegate = infobar_service->infobar_at(i)->AsTranslateInfoBarDelegate(); 73 if (old_delegate) { 74 if (!replace_existing_infobar) 75 return; 76 break; 77 } 78 } 79 80 // Create the new delegate. 81 scoped_ptr<TranslateInfoBarDelegate> infobar( 82 new TranslateInfoBarDelegate(infobar_service, infobar_type, old_delegate, 83 original_language, target_language, 84 error_type, prefs, shortcut_config)); 85 86 // Do not create the after translate infobar if we are auto translating. 87 if ((infobar_type == TranslateInfoBarDelegate::AFTER_TRANSLATE) || 88 (infobar_type == TranslateInfoBarDelegate::TRANSLATING)) { 89 TranslateTabHelper* translate_tab_helper = 90 TranslateTabHelper::FromWebContents(infobar_service->web_contents()); 91 if (!translate_tab_helper || 92 translate_tab_helper->language_state().InTranslateNavigation()) 93 return; 94 } 95 96 // Add the new delegate if necessary. 97 if (!old_delegate) { 98 infobar_service->AddInfoBar(infobar.PassAs<InfoBarDelegate>()); 99 } else { 100 DCHECK(replace_existing_infobar); 101 infobar_service->ReplaceInfoBar(old_delegate, 102 infobar.PassAs<InfoBarDelegate>()); 103 } 104 } 105 106 void TranslateInfoBarDelegate::Translate() { 107 if (!web_contents()->GetBrowserContext()->IsOffTheRecord()) { 108 prefs_.ResetTranslationDeniedCount(original_language_code()); 109 prefs_.IncrementTranslationAcceptedCount(original_language_code()); 110 } 111 112 TranslateManager::GetInstance()->TranslatePage(web_contents(), 113 original_language_code(), 114 target_language_code()); 115 116 UMA_HISTOGRAM_BOOLEAN(kPerformTranslate, true); 117 } 118 119 void TranslateInfoBarDelegate::RevertTranslation() { 120 TranslateManager::GetInstance()->RevertTranslation(web_contents()); 121 RemoveSelf(); 122 123 UMA_HISTOGRAM_BOOLEAN(kRevertTranslation, true); 124 } 125 126 void TranslateInfoBarDelegate::ReportLanguageDetectionError() { 127 TranslateManager::GetInstance()->ReportLanguageDetectionError( 128 web_contents()); 129 } 130 131 void TranslateInfoBarDelegate::TranslationDeclined() { 132 if (!web_contents()->GetBrowserContext()->IsOffTheRecord()) { 133 prefs_.ResetTranslationAcceptedCount(original_language_code()); 134 prefs_.IncrementTranslationDeniedCount(original_language_code()); 135 } 136 137 // Remember that the user declined the translation so as to prevent showing a 138 // translate infobar for that page again. (TranslateManager initiates 139 // translations when getting a LANGUAGE_DETERMINED from the page, which 140 // happens when a load stops. That could happen multiple times, including 141 // after the user already declined the translation.) 142 TranslateTabHelper::FromWebContents(web_contents())-> 143 language_state().set_translation_declined(true); 144 145 UMA_HISTOGRAM_BOOLEAN(kDeclineTranslate, true); 146 } 147 148 bool TranslateInfoBarDelegate::IsTranslatableLanguageByPrefs() { 149 Profile* profile = 150 Profile::FromBrowserContext(web_contents()->GetBrowserContext()); 151 Profile* original_profile = profile->GetOriginalProfile(); 152 return TranslatePrefs::CanTranslateLanguage(original_profile, 153 original_language_code()); 154 } 155 156 void TranslateInfoBarDelegate::ToggleTranslatableLanguageByPrefs() { 157 const std::string& original_lang = original_language_code(); 158 if (prefs_.IsBlockedLanguage(original_lang)) { 159 prefs_.UnblockLanguage(original_lang); 160 } else { 161 prefs_.BlockLanguage(original_lang); 162 RemoveSelf(); 163 } 164 165 UMA_HISTOGRAM_BOOLEAN(kNeverTranslateLang, true); 166 } 167 168 bool TranslateInfoBarDelegate::IsSiteBlacklisted() { 169 std::string host = GetPageHost(); 170 return !host.empty() && prefs_.IsSiteBlacklisted(host); 171 } 172 173 void TranslateInfoBarDelegate::ToggleSiteBlacklist() { 174 std::string host = GetPageHost(); 175 if (host.empty()) 176 return; 177 178 if (prefs_.IsSiteBlacklisted(host)) { 179 prefs_.RemoveSiteFromBlacklist(host); 180 } else { 181 prefs_.BlacklistSite(host); 182 RemoveSelf(); 183 } 184 185 UMA_HISTOGRAM_BOOLEAN(kNeverTranslateSite, true); 186 } 187 188 bool TranslateInfoBarDelegate::ShouldAlwaysTranslate() { 189 return prefs_.IsLanguagePairWhitelisted(original_language_code(), 190 target_language_code()); 191 } 192 193 void TranslateInfoBarDelegate::ToggleAlwaysTranslate() { 194 const std::string& original_lang = original_language_code(); 195 const std::string& target_lang = target_language_code(); 196 if (prefs_.IsLanguagePairWhitelisted(original_lang, target_lang)) 197 prefs_.RemoveLanguagePairFromWhitelist(original_lang, target_lang); 198 else 199 prefs_.WhitelistLanguagePair(original_lang, target_lang); 200 201 UMA_HISTOGRAM_BOOLEAN(kAlwaysTranslateLang, true); 202 } 203 204 void TranslateInfoBarDelegate::AlwaysTranslatePageLanguage() { 205 const std::string& original_lang = original_language_code(); 206 const std::string& target_lang = target_language_code(); 207 DCHECK(!prefs_.IsLanguagePairWhitelisted(original_lang, target_lang)); 208 prefs_.WhitelistLanguagePair(original_lang, target_lang); 209 Translate(); 210 } 211 212 void TranslateInfoBarDelegate::NeverTranslatePageLanguage() { 213 std::string original_lang = original_language_code(); 214 DCHECK(!prefs_.IsBlockedLanguage(original_lang)); 215 prefs_.BlockLanguage(original_lang); 216 RemoveSelf(); 217 } 218 219 string16 TranslateInfoBarDelegate::GetMessageInfoBarText() { 220 if (infobar_type_ == TRANSLATING) { 221 return l10n_util::GetStringFUTF16(IDS_TRANSLATE_INFOBAR_TRANSLATING_TO, 222 language_name_at(target_language_index_)); 223 } 224 225 DCHECK_EQ(TRANSLATION_ERROR, infobar_type_); 226 UMA_HISTOGRAM_ENUMERATION(kShowErrorInfobar, 227 error_type_, 228 TranslateErrors::TRANSLATE_ERROR_MAX); 229 switch (error_type_) { 230 case TranslateErrors::NETWORK: 231 return l10n_util::GetStringUTF16( 232 IDS_TRANSLATE_INFOBAR_ERROR_CANT_CONNECT); 233 case TranslateErrors::INITIALIZATION_ERROR: 234 case TranslateErrors::TRANSLATION_ERROR: 235 return l10n_util::GetStringUTF16( 236 IDS_TRANSLATE_INFOBAR_ERROR_CANT_TRANSLATE); 237 case TranslateErrors::UNKNOWN_LANGUAGE: 238 return l10n_util::GetStringUTF16( 239 IDS_TRANSLATE_INFOBAR_UNKNOWN_PAGE_LANGUAGE); 240 case TranslateErrors::UNSUPPORTED_LANGUAGE: 241 return l10n_util::GetStringFUTF16( 242 IDS_TRANSLATE_INFOBAR_UNSUPPORTED_PAGE_LANGUAGE, 243 language_name_at(target_language_index_)); 244 case TranslateErrors::IDENTICAL_LANGUAGES: 245 return l10n_util::GetStringFUTF16( 246 IDS_TRANSLATE_INFOBAR_ERROR_SAME_LANGUAGE, 247 language_name_at(target_language_index_)); 248 default: 249 NOTREACHED(); 250 return string16(); 251 } 252 } 253 254 string16 TranslateInfoBarDelegate::GetMessageInfoBarButtonText() { 255 if (infobar_type_ != TRANSLATION_ERROR) { 256 DCHECK_EQ(TRANSLATING, infobar_type_); 257 } else if ((error_type_ != TranslateErrors::IDENTICAL_LANGUAGES) && 258 (error_type_ != TranslateErrors::UNKNOWN_LANGUAGE)) { 259 return l10n_util::GetStringUTF16( 260 (error_type_ == TranslateErrors::UNSUPPORTED_LANGUAGE) ? 261 IDS_TRANSLATE_INFOBAR_REVERT : IDS_TRANSLATE_INFOBAR_RETRY); 262 } 263 return string16(); 264 } 265 266 void TranslateInfoBarDelegate::MessageInfoBarButtonPressed() { 267 DCHECK_EQ(TRANSLATION_ERROR, infobar_type_); 268 if (error_type_ == TranslateErrors::UNSUPPORTED_LANGUAGE) { 269 RevertTranslation(); 270 return; 271 } 272 // This is the "Try again..." case. 273 TranslateManager::GetInstance()->TranslatePage( 274 web_contents(), original_language_code(), target_language_code()); 275 } 276 277 bool TranslateInfoBarDelegate::ShouldShowMessageInfoBarButton() { 278 return !GetMessageInfoBarButtonText().empty(); 279 } 280 281 bool TranslateInfoBarDelegate::ShouldShowNeverTranslateShortcut() { 282 DCHECK_EQ(BEFORE_TRANSLATE, infobar_type_); 283 return !web_contents()->GetBrowserContext()->IsOffTheRecord() && 284 (prefs_.GetTranslationDeniedCount(original_language_code()) >= 285 shortcut_config_.never_translate_min_count); 286 } 287 288 bool TranslateInfoBarDelegate::ShouldShowAlwaysTranslateShortcut() { 289 DCHECK_EQ(BEFORE_TRANSLATE, infobar_type_); 290 return !web_contents()->GetBrowserContext()->IsOffTheRecord() && 291 (prefs_.GetTranslationAcceptedCount(original_language_code()) >= 292 shortcut_config_.always_translate_min_count); 293 } 294 295 // static 296 string16 TranslateInfoBarDelegate::GetLanguageDisplayableName( 297 const std::string& language_code) { 298 return l10n_util::GetDisplayNameForLocale( 299 language_code, g_browser_process->GetApplicationLocale(), true); 300 } 301 302 // static 303 void TranslateInfoBarDelegate::GetAfterTranslateStrings( 304 std::vector<string16>* strings, 305 bool* swap_languages, 306 bool autodetermined_source_language) { 307 DCHECK(strings); 308 309 if (autodetermined_source_language) { 310 size_t offset; 311 string16 text = l10n_util::GetStringFUTF16( 312 IDS_TRANSLATE_INFOBAR_AFTER_MESSAGE_AUTODETERMINED_SOURCE_LANGUAGE, 313 string16(), 314 &offset); 315 316 strings->push_back(text.substr(0, offset)); 317 strings->push_back(text.substr(offset)); 318 return; 319 } 320 DCHECK(swap_languages); 321 322 std::vector<size_t> offsets; 323 string16 text = l10n_util::GetStringFUTF16( 324 IDS_TRANSLATE_INFOBAR_AFTER_MESSAGE, string16(), string16(), &offsets); 325 DCHECK_EQ(2U, offsets.size()); 326 327 *swap_languages = (offsets[0] > offsets[1]); 328 if (*swap_languages) 329 std::swap(offsets[0], offsets[1]); 330 331 strings->push_back(text.substr(0, offsets[0])); 332 strings->push_back(text.substr(offsets[0], offsets[1] - offsets[0])); 333 strings->push_back(text.substr(offsets[1])); 334 } 335 336 TranslateInfoBarDelegate::TranslateInfoBarDelegate( 337 InfoBarService* infobar_service, 338 Type infobar_type, 339 TranslateInfoBarDelegate* old_delegate, 340 const std::string& original_language, 341 const std::string& target_language, 342 TranslateErrors::Type error_type, 343 PrefService* prefs, 344 ShortcutConfiguration shortcut_config) 345 : InfoBarDelegate(infobar_service), 346 infobar_type_(infobar_type), 347 background_animation_(NONE), 348 original_language_index_(kNoIndex), 349 initial_original_language_index_(kNoIndex), 350 target_language_index_(kNoIndex), 351 error_type_(error_type), 352 prefs_(prefs), 353 shortcut_config_(shortcut_config) { 354 DCHECK_NE((infobar_type_ == TRANSLATION_ERROR), 355 (error_type_ == TranslateErrors::NONE)); 356 357 if (old_delegate && (old_delegate->is_error() != is_error())) 358 background_animation_ = is_error() ? NORMAL_TO_ERROR : ERROR_TO_NORMAL; 359 360 std::vector<std::string> language_codes; 361 TranslateManager::GetSupportedLanguages(&language_codes); 362 363 // Preparing for the alphabetical order in the locale. 364 UErrorCode error = U_ZERO_ERROR; 365 std::string locale = g_browser_process->GetApplicationLocale(); 366 icu::Locale loc(locale.c_str()); 367 scoped_ptr<icu::Collator> collator(icu::Collator::createInstance(loc, error)); 368 collator->setStrength(icu::Collator::PRIMARY); 369 370 languages_.reserve(language_codes.size()); 371 for (std::vector<std::string>::const_iterator iter = language_codes.begin(); 372 iter != language_codes.end(); ++iter) { 373 std::string language_code = *iter; 374 375 string16 language_name = GetLanguageDisplayableName(language_code); 376 // Insert the language in languages_ in alphabetical order. 377 std::vector<LanguageNamePair>::iterator iter2; 378 for (iter2 = languages_.begin(); iter2 != languages_.end(); ++iter2) { 379 if (base::i18n::CompareString16WithCollator(collator.get(), 380 language_name, iter2->second) == UCOL_LESS) { 381 break; 382 } 383 } 384 languages_.insert(iter2, LanguageNamePair(language_code, language_name)); 385 } 386 for (std::vector<LanguageNamePair>::const_iterator iter = languages_.begin(); 387 iter != languages_.end(); ++iter) { 388 std::string language_code = iter->first; 389 if (language_code == original_language) { 390 original_language_index_ = iter - languages_.begin(); 391 initial_original_language_index_ = original_language_index_; 392 } 393 if (language_code == target_language) 394 target_language_index_ = iter - languages_.begin(); 395 } 396 DCHECK_NE(kNoIndex, target_language_index_); 397 } 398 399 void TranslateInfoBarDelegate::InfoBarDismissed() { 400 if (infobar_type_ != BEFORE_TRANSLATE) 401 return; 402 403 // The user closed the infobar without clicking the translate button. 404 TranslationDeclined(); 405 UMA_HISTOGRAM_BOOLEAN(kCloseInfobar, true); 406 } 407 408 int TranslateInfoBarDelegate::GetIconID() const { 409 return IDR_INFOBAR_TRANSLATE; 410 } 411 412 InfoBarDelegate::Type TranslateInfoBarDelegate::GetInfoBarType() const { 413 return PAGE_ACTION_TYPE; 414 } 415 416 bool TranslateInfoBarDelegate::ShouldExpire( 417 const content::LoadCommittedDetails& details) const { 418 // Note: we allow closing this infobar even if the main frame navigation 419 // was programmatic and not initiated by the user - crbug.com/70261 . 420 if (!details.is_navigation_to_different_page() && !details.is_main_frame) 421 return false; 422 423 return InfoBarDelegate::ShouldExpireInternal(details); 424 } 425 426 TranslateInfoBarDelegate* 427 TranslateInfoBarDelegate::AsTranslateInfoBarDelegate() { 428 return this; 429 } 430 431 std::string TranslateInfoBarDelegate::GetPageHost() { 432 NavigationEntry* entry = web_contents()->GetController().GetActiveEntry(); 433 return entry ? entry->GetURL().HostNoBrackets() : std::string(); 434 } 435