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_infobar_delegate.h"
6 
7 #include <algorithm>
8 #include <utility>
9 
10 #include "base/i18n/string_compare.h"
11 #include "base/memory/ptr_util.h"
12 #include "base/metrics/field_trial_params.h"
13 #include "base/metrics/histogram_macros.h"
14 #include "build/build_config.h"
15 #include "components/infobars/core/infobar.h"
16 #include "components/infobars/core/infobar_manager.h"
17 #include "components/strings/grit/components_strings.h"
18 #include "components/translate/core/browser/language_state.h"
19 #include "components/translate/core/browser/translate_accept_languages.h"
20 #include "components/translate/core/browser/translate_client.h"
21 #include "components/translate/core/browser/translate_download_manager.h"
22 #include "components/translate/core/browser/translate_driver.h"
23 #include "components/translate/core/browser/translate_manager.h"
24 #include "components/translate/core/common/translate_constants.h"
25 #include "ui/base/l10n/l10n_util.h"
26 
27 namespace {
28 
29 // The default number of times user should consecutively translate for "Always
30 // Translate" to automatically trigger.
31 const int kAutoAlwaysThreshold = 5;
32 // The default number of times user should consecutively dismiss the translate
33 // infobar for "Never Translate" to automatically trigger.
34 const int kAutoNeverThreshold = 20;
35 // The default maximum number of times "Always Translate" is automatically
36 // triggered.
37 const int kMaxNumberOfAutoAlways = 2;
38 // The default maximum number of times "Never Translate" is automatically
39 // triggered.
40 const int kMaxNumberOfAutoNever = 2;
41 
42 }  // namespace
43 
44 namespace translate {
45 
46 const base::Feature kTranslateAutoSnackbars{"TranslateAutoSnackbars",
47                                             base::FEATURE_ENABLED_BY_DEFAULT};
48 
49 const base::Feature kTranslateCompactUI{"TranslateCompactUI",
50                                         base::FEATURE_ENABLED_BY_DEFAULT};
51 
52 const size_t TranslateInfoBarDelegate::kNoIndex = TranslateUIDelegate::kNoIndex;
53 
~TranslateInfoBarDelegate()54 TranslateInfoBarDelegate::~TranslateInfoBarDelegate() {
55   for (auto& observer : observers_) {
56     observer.OnTranslateInfoBarDelegateDestroyed(this);
57   }
58 }
59 
60 infobars::InfoBarDelegate::InfoBarIdentifier
GetIdentifier() const61 TranslateInfoBarDelegate::GetIdentifier() const {
62   return TRANSLATE_INFOBAR_DELEGATE_NON_AURA;
63 }
64 
65 // static
Create(bool replace_existing_infobar,const base::WeakPtr<TranslateManager> & translate_manager,infobars::InfoBarManager * infobar_manager,bool is_off_the_record,translate::TranslateStep step,const std::string & original_language,const std::string & target_language,TranslateErrors::Type error_type,bool triggered_from_menu)66 void TranslateInfoBarDelegate::Create(
67     bool replace_existing_infobar,
68     const base::WeakPtr<TranslateManager>& translate_manager,
69     infobars::InfoBarManager* infobar_manager,
70     bool is_off_the_record,
71     translate::TranslateStep step,
72     const std::string& original_language,
73     const std::string& target_language,
74     TranslateErrors::Type error_type,
75     bool triggered_from_menu) {
76   DCHECK(translate_manager);
77   DCHECK(infobar_manager);
78 
79   // Check preconditions.
80   if (step != translate::TRANSLATE_STEP_TRANSLATE_ERROR) {
81     DCHECK(TranslateDownloadManager::IsSupportedLanguage(target_language));
82     if (!TranslateDownloadManager::IsSupportedLanguage(original_language)) {
83       // The original language can only be "unknown" for the "translating"
84       // infobar, which is the case when the user started a translation from the
85       // context menu.
86       DCHECK(step == translate::TRANSLATE_STEP_TRANSLATING ||
87              step == translate::TRANSLATE_STEP_AFTER_TRANSLATE);
88       DCHECK_EQ(translate::kUnknownLanguageCode, original_language);
89     }
90   }
91 
92   // Find any existing translate infobar delegate.
93   infobars::InfoBar* old_infobar = NULL;
94   TranslateInfoBarDelegate* old_delegate = NULL;
95   for (size_t i = 0; i < infobar_manager->infobar_count(); ++i) {
96     old_infobar = infobar_manager->infobar_at(i);
97     old_delegate = old_infobar->delegate()->AsTranslateInfoBarDelegate();
98     if (old_delegate) {
99       if (!replace_existing_infobar)
100         return;
101       break;
102     }
103   }
104 
105   // Try to reuse existing translate infobar delegate.
106   if (old_delegate) {
107     old_delegate->step_ = step;
108     for (auto& observer : old_delegate->observers_) {
109       observer.OnTargetLanguageChanged(target_language);
110       observer.OnTranslateStepChanged(step, error_type);
111     }
112     return;
113   }
114 
115   // Add the new delegate.
116   TranslateClient* translate_client = translate_manager->translate_client();
117   std::unique_ptr<infobars::InfoBar> infobar(translate_client->CreateInfoBar(
118       base::WrapUnique(new TranslateInfoBarDelegate(
119           translate_manager, is_off_the_record, step, original_language,
120           target_language, error_type, triggered_from_menu))));
121   infobar_manager->AddInfoBar(std::move(infobar));
122 }
123 
num_languages() const124 size_t TranslateInfoBarDelegate::num_languages() const {
125   return ui_delegate_.GetNumberOfLanguages();
126 }
127 
language_code_at(size_t index) const128 std::string TranslateInfoBarDelegate::language_code_at(size_t index) const {
129   return ui_delegate_.GetLanguageCodeAt(index);
130 }
131 
language_name_at(size_t index) const132 base::string16 TranslateInfoBarDelegate::language_name_at(size_t index) const {
133   return ui_delegate_.GetLanguageNameAt(index);
134 }
135 
original_language_name() const136 base::string16 TranslateInfoBarDelegate::original_language_name() const {
137   return language_name_at(ui_delegate_.GetOriginalLanguageIndex());
138 }
139 
target_language_name() const140 base::string16 TranslateInfoBarDelegate::target_language_name() const {
141   return language_name_at(ui_delegate_.GetTargetLanguageIndex());
142 }
143 
UpdateOriginalLanguage(const std::string & language_code)144 void TranslateInfoBarDelegate::UpdateOriginalLanguage(
145     const std::string& language_code) {
146   ui_delegate_.UpdateOriginalLanguage(language_code);
147 }
148 
UpdateTargetLanguage(const std::string & language_code)149 void TranslateInfoBarDelegate::UpdateTargetLanguage(
150     const std::string& language_code) {
151   ui_delegate_.UpdateTargetLanguage(language_code);
152 }
153 
OnErrorShown(TranslateErrors::Type error_type)154 void TranslateInfoBarDelegate::OnErrorShown(TranslateErrors::Type error_type) {
155   ui_delegate_.OnErrorShown(error_type);
156 }
157 
Translate()158 void TranslateInfoBarDelegate::Translate() {
159   ui_delegate_.Translate();
160 }
161 
RevertTranslation()162 void TranslateInfoBarDelegate::RevertTranslation() {
163   ui_delegate_.RevertTranslation();
164   infobar()->RemoveSelf();
165 }
166 
RevertWithoutClosingInfobar()167 void TranslateInfoBarDelegate::RevertWithoutClosingInfobar() {
168   ui_delegate_.RevertTranslation();
169   step_ = TRANSLATE_STEP_BEFORE_TRANSLATE;
170 }
171 
ReportLanguageDetectionError()172 void TranslateInfoBarDelegate::ReportLanguageDetectionError() {
173   if (translate_manager_)
174     translate_manager_->ReportLanguageDetectionError();
175 }
176 
TranslationDeclined()177 void TranslateInfoBarDelegate::TranslationDeclined() {
178   ui_delegate_.TranslationDeclined(true);
179 }
180 
IsTranslatableLanguageByPrefs() const181 bool TranslateInfoBarDelegate::IsTranslatableLanguageByPrefs() const {
182   TranslateClient* client = translate_manager_->translate_client();
183   std::unique_ptr<TranslatePrefs> translate_prefs(client->GetTranslatePrefs());
184   TranslateAcceptLanguages* accept_languages =
185       client->GetTranslateAcceptLanguages();
186   return translate_prefs->CanTranslateLanguage(accept_languages,
187                                                original_language_code());
188 }
189 
ToggleTranslatableLanguageByPrefs()190 void TranslateInfoBarDelegate::ToggleTranslatableLanguageByPrefs() {
191   ui_delegate_.SetLanguageBlocked(!ui_delegate_.IsLanguageBlocked());
192 }
193 
IsSiteBlacklisted() const194 bool TranslateInfoBarDelegate::IsSiteBlacklisted() const {
195   return ui_delegate_.IsSiteBlacklisted();
196 }
197 
ToggleSiteBlacklist()198 void TranslateInfoBarDelegate::ToggleSiteBlacklist() {
199   ui_delegate_.SetSiteBlacklist(!ui_delegate_.IsSiteBlacklisted());
200 }
201 
ShouldAlwaysTranslate() const202 bool TranslateInfoBarDelegate::ShouldAlwaysTranslate() const {
203   return ui_delegate_.ShouldAlwaysTranslate();
204 }
205 
ToggleAlwaysTranslate()206 void TranslateInfoBarDelegate::ToggleAlwaysTranslate() {
207   ui_delegate_.SetAlwaysTranslate(!ui_delegate_.ShouldAlwaysTranslate());
208 }
209 
AlwaysTranslatePageLanguage()210 void TranslateInfoBarDelegate::AlwaysTranslatePageLanguage() {
211   DCHECK(!ui_delegate_.ShouldAlwaysTranslate());
212   ui_delegate_.SetAlwaysTranslate(true);
213   Translate();
214 }
215 
NeverTranslatePageLanguage()216 void TranslateInfoBarDelegate::NeverTranslatePageLanguage() {
217   DCHECK(!ui_delegate_.IsLanguageBlocked());
218   ui_delegate_.SetLanguageBlocked(true);
219   infobar()->RemoveSelf();
220 }
221 
GetMessageInfoBarButtonText()222 base::string16 TranslateInfoBarDelegate::GetMessageInfoBarButtonText() {
223   if (step_ != translate::TRANSLATE_STEP_TRANSLATE_ERROR) {
224     DCHECK_EQ(translate::TRANSLATE_STEP_TRANSLATING, step_);
225   } else if ((error_type_ != TranslateErrors::IDENTICAL_LANGUAGES) &&
226              (error_type_ != TranslateErrors::UNKNOWN_LANGUAGE)) {
227     return l10n_util::GetStringUTF16(
228         (error_type_ == TranslateErrors::UNSUPPORTED_LANGUAGE)
229             ? IDS_TRANSLATE_INFOBAR_REVERT
230             : IDS_TRANSLATE_INFOBAR_RETRY);
231   }
232   return base::string16();
233 }
234 
MessageInfoBarButtonPressed()235 void TranslateInfoBarDelegate::MessageInfoBarButtonPressed() {
236   DCHECK_EQ(translate::TRANSLATE_STEP_TRANSLATE_ERROR, step_);
237   if (error_type_ == TranslateErrors::UNSUPPORTED_LANGUAGE) {
238     RevertTranslation();
239     return;
240   }
241   // This is the "Try again..." case.
242   DCHECK(translate_manager_);
243   translate_manager_->TranslatePage(original_language_code(),
244                                     target_language_code(), false);
245 }
246 
ShouldShowMessageInfoBarButton()247 bool TranslateInfoBarDelegate::ShouldShowMessageInfoBarButton() {
248   return !GetMessageInfoBarButtonText().empty();
249 }
250 
ShouldShowAlwaysTranslateShortcut()251 bool TranslateInfoBarDelegate::ShouldShowAlwaysTranslateShortcut() {
252 #if defined(OS_IOS)
253   // On mobile, the option to always translate is shown after the translation.
254   DCHECK_EQ(translate::TRANSLATE_STEP_AFTER_TRANSLATE, step_);
255 #else
256   // On desktop, the option to always translate is shown before the translation.
257   DCHECK_EQ(translate::TRANSLATE_STEP_BEFORE_TRANSLATE, step_);
258 #endif
259   return ui_delegate_.ShouldShowAlwaysTranslateShortcut();
260 }
261 
ShouldShowNeverTranslateShortcut()262 bool TranslateInfoBarDelegate::ShouldShowNeverTranslateShortcut() {
263   DCHECK_EQ(translate::TRANSLATE_STEP_BEFORE_TRANSLATE, step_);
264   return ui_delegate_.ShouldShowNeverTranslateShortcut();
265 }
266 
267 #if defined(OS_IOS)
ShowNeverTranslateInfobar()268 void TranslateInfoBarDelegate::ShowNeverTranslateInfobar() {
269   // Return if the infobar is not owned.
270   if (!infobar()->owner())
271     return;
272 
273   Create(true, translate_manager_, infobar()->owner(), is_off_the_record_,
274          translate::TRANSLATE_STEP_NEVER_TRANSLATE, original_language_code(),
275          target_language_code(), TranslateErrors::NONE, false);
276 }
277 #endif
278 
GetTranslationAcceptedCount()279 int TranslateInfoBarDelegate::GetTranslationAcceptedCount() {
280   return prefs_->GetTranslationAcceptedCount(original_language_code());
281 }
282 
GetTranslationDeniedCount()283 int TranslateInfoBarDelegate::GetTranslationDeniedCount() {
284   return prefs_->GetTranslationDeniedCount(original_language_code());
285 }
286 
ResetTranslationAcceptedCount()287 void TranslateInfoBarDelegate::ResetTranslationAcceptedCount() {
288   prefs_->ResetTranslationAcceptedCount(original_language_code());
289 }
290 
ResetTranslationDeniedCount()291 void TranslateInfoBarDelegate::ResetTranslationDeniedCount() {
292   prefs_->ResetTranslationDeniedCount(original_language_code());
293 }
294 
ShouldAutoAlwaysTranslate()295 bool TranslateInfoBarDelegate::ShouldAutoAlwaysTranslate() {
296   // Don't trigger if it's off the record or already set to always translate.
297   if (is_off_the_record() || ShouldAlwaysTranslate()) {
298     return false;
299   }
300 
301   bool always_translate =
302       (GetTranslationAcceptedCount() >= GetAutoAlwaysThreshold() &&
303        GetTranslationAutoAlwaysCount() < GetMaximumNumberOfAutoAlways());
304 
305   if (always_translate) {
306     // Auto-always will be triggered. Need to increment the auto-always counter.
307     IncrementTranslationAutoAlwaysCount();
308     // Reset translateAcceptedCount so that auto-always could be triggered
309     // again.
310     ResetTranslationAcceptedCount();
311   }
312   return always_translate;
313 }
314 
ShouldAutoNeverTranslate()315 bool TranslateInfoBarDelegate::ShouldAutoNeverTranslate() {
316   // Don't trigger if it's off the record or language already blocked.
317   if (is_off_the_record() || !IsTranslatableLanguageByPrefs()) {
318     return false;
319   }
320 
321   int auto_never_count = GetTranslationAutoNeverCount();
322 
323   // At the beginning (auto_never_count == 0), deniedCount starts at 0 and is
324   // off-by-one (because this checking is done before increment). However, after
325   // auto-never is triggered once (auto_never_count > 0), deniedCount starts at
326   // 1.  So there is no off-by-one by then.
327   int off_by_one = auto_never_count == 0 ? 1 : 0;
328 
329   bool never_translate =
330       (GetTranslationDeniedCount() + off_by_one >= GetAutoNeverThreshold() &&
331        auto_never_count < GetMaximumNumberOfAutoNever());
332   if (never_translate) {
333     // Auto-never will be triggered. Need to increment the auto-never counter.
334     IncrementTranslationAutoNeverCount();
335     // Reset translateDeniedCount so that auto-never could be triggered again.
336     ResetTranslationDeniedCount();
337   }
338   return never_translate;
339 }
340 
GetTranslationAutoAlwaysCount()341 int TranslateInfoBarDelegate::GetTranslationAutoAlwaysCount() {
342   return prefs_->GetTranslationAutoAlwaysCount(original_language_code());
343 }
344 
GetTranslationAutoNeverCount()345 int TranslateInfoBarDelegate::GetTranslationAutoNeverCount() {
346   return prefs_->GetTranslationAutoNeverCount(original_language_code());
347 }
348 
IncrementTranslationAutoAlwaysCount()349 void TranslateInfoBarDelegate::IncrementTranslationAutoAlwaysCount() {
350   prefs_->IncrementTranslationAutoAlwaysCount(original_language_code());
351 }
352 
IncrementTranslationAutoNeverCount()353 void TranslateInfoBarDelegate::IncrementTranslationAutoNeverCount() {
354   prefs_->IncrementTranslationAutoNeverCount(original_language_code());
355 }
356 
357 // static
GetAfterTranslateStrings(std::vector<base::string16> * strings,bool * swap_languages,bool autodetermined_source_language)358 void TranslateInfoBarDelegate::GetAfterTranslateStrings(
359     std::vector<base::string16>* strings,
360     bool* swap_languages,
361     bool autodetermined_source_language) {
362   DCHECK(strings);
363 
364   if (autodetermined_source_language) {
365     size_t offset;
366     base::string16 text = l10n_util::GetStringFUTF16(
367         IDS_TRANSLATE_INFOBAR_AFTER_MESSAGE_AUTODETERMINED_SOURCE_LANGUAGE,
368         base::string16(), &offset);
369 
370     strings->push_back(text.substr(0, offset));
371     strings->push_back(text.substr(offset));
372     return;
373   }
374   DCHECK(swap_languages);
375 
376   std::vector<size_t> offsets;
377   base::string16 text =
378       l10n_util::GetStringFUTF16(IDS_TRANSLATE_INFOBAR_AFTER_MESSAGE,
379                                  base::string16(), base::string16(), &offsets);
380   DCHECK_EQ(2U, offsets.size());
381 
382   *swap_languages = (offsets[0] > offsets[1]);
383   if (*swap_languages)
384     std::swap(offsets[0], offsets[1]);
385 
386   strings->push_back(text.substr(0, offsets[0]));
387   strings->push_back(text.substr(offsets[0], offsets[1] - offsets[0]));
388   strings->push_back(text.substr(offsets[1]));
389 }
390 
GetTranslateDriver()391 TranslateDriver* TranslateInfoBarDelegate::GetTranslateDriver() {
392   if (!translate_manager_)
393     return NULL;
394 
395   return translate_manager_->translate_client()->GetTranslateDriver();
396 }
397 
AddObserver(Observer * observer)398 void TranslateInfoBarDelegate::AddObserver(Observer* observer) {
399   observers_.AddObserver(observer);
400 }
401 
RemoveObserver(Observer * observer)402 void TranslateInfoBarDelegate::RemoveObserver(Observer* observer) {
403   observers_.RemoveObserver(observer);
404 }
405 
TranslateInfoBarDelegate(const base::WeakPtr<TranslateManager> & translate_manager,bool is_off_the_record,translate::TranslateStep step,const std::string & original_language,const std::string & target_language,TranslateErrors::Type error_type,bool triggered_from_menu)406 TranslateInfoBarDelegate::TranslateInfoBarDelegate(
407     const base::WeakPtr<TranslateManager>& translate_manager,
408     bool is_off_the_record,
409     translate::TranslateStep step,
410     const std::string& original_language,
411     const std::string& target_language,
412     TranslateErrors::Type error_type,
413     bool triggered_from_menu)
414     : infobars::InfoBarDelegate(),
415       is_off_the_record_(is_off_the_record),
416       step_(step),
417       ui_delegate_(translate_manager, original_language, target_language),
418       translate_manager_(translate_manager),
419       error_type_(error_type),
420       prefs_(translate_manager->translate_client()->GetTranslatePrefs()),
421       triggered_from_menu_(triggered_from_menu) {
422   DCHECK_NE((step_ == translate::TRANSLATE_STEP_TRANSLATE_ERROR),
423             (error_type_ == TranslateErrors::NONE));
424   DCHECK(translate_manager_);
425 }
426 
GetIconId() const427 int TranslateInfoBarDelegate::GetIconId() const {
428   return translate_manager_->translate_client()->GetInfobarIconID();
429 }
430 
InfoBarDismissed()431 void TranslateInfoBarDelegate::InfoBarDismissed() {
432   OnInfoBarClosedByUser();
433 
434   bool declined = false;
435   bool has_observer = false;
436   for (auto& observer : observers_) {
437     has_observer = true;
438     if (observer.IsDeclinedByUser())
439       declined = true;
440   }
441 
442   if (!has_observer)
443     declined = step_ == translate::TRANSLATE_STEP_BEFORE_TRANSLATE;
444 
445   if (declined) {
446     // The user closed the infobar without clicking the translate button.
447     TranslationDeclined();
448     UMA_HISTOGRAM_BOOLEAN("Translate.DeclineTranslateCloseInfobar", true);
449   }
450 }
451 
452 TranslateInfoBarDelegate*
AsTranslateInfoBarDelegate()453 TranslateInfoBarDelegate::AsTranslateInfoBarDelegate() {
454   return this;
455 }
456 
GetAutoAlwaysThreshold()457 int TranslateInfoBarDelegate::GetAutoAlwaysThreshold() {
458   static constexpr base::FeatureParam<int> auto_always_threshold{
459       &kTranslateAutoSnackbars, "AutoAlwaysThreshold", kAutoAlwaysThreshold};
460   return auto_always_threshold.Get();
461 }
462 
GetAutoNeverThreshold()463 int TranslateInfoBarDelegate::GetAutoNeverThreshold() {
464   static constexpr base::FeatureParam<int> auto_never_threshold{
465       &kTranslateAutoSnackbars, "AutoNeverThreshold", kAutoNeverThreshold};
466   return auto_never_threshold.Get();
467 }
468 
GetMaximumNumberOfAutoAlways()469 int TranslateInfoBarDelegate::GetMaximumNumberOfAutoAlways() {
470   static constexpr base::FeatureParam<int> auto_always_maximum{
471       &kTranslateAutoSnackbars, "AutoAlwaysMaximum", kMaxNumberOfAutoAlways};
472   return auto_always_maximum.Get();
473 }
474 
GetMaximumNumberOfAutoNever()475 int TranslateInfoBarDelegate::GetMaximumNumberOfAutoNever() {
476   static constexpr base::FeatureParam<int> auto_never_maximum{
477       &kTranslateAutoSnackbars, "AutoNeverMaximum", kMaxNumberOfAutoNever};
478   return auto_never_maximum.Get();
479 }
480 
OnInfoBarClosedByUser()481 void TranslateInfoBarDelegate::OnInfoBarClosedByUser() {
482   ui_delegate_.OnUIClosedByUser();
483 }
484 
485 }  // namespace translate
486