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