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 <map>
8 #include <memory>
9 #include <tuple>
10 #include <utility>
11 
12 #include "base/bind.h"
13 #include "base/command_line.h"
14 #include "base/metrics/field_trial.h"
15 #include "base/metrics/field_trial_params.h"
16 #include "base/metrics/histogram.h"
17 #include "base/metrics/histogram_macros.h"
18 #include "base/stl_util.h"
19 #include "base/strings/string_number_conversions.h"
20 #include "base/strings/string_piece.h"
21 #include "base/strings/string_split.h"
22 #include "base/strings/stringprintf.h"
23 #include "base/time/time.h"
24 #include "build/build_config.h"
25 #include "components/language/core/browser/language_model.h"
26 #include "components/language/core/common/language_experiments.h"
27 #include "components/language/core/common/language_util.h"
28 #include "components/language/core/common/locale_util.h"
29 #include "components/prefs/pref_service.h"
30 #include "components/translate/core/browser/language_state.h"
31 #include "components/translate/core/browser/page_translated_details.h"
32 #include "components/translate/core/browser/translate_accept_languages.h"
33 #include "components/translate/core/browser/translate_browser_metrics.h"
34 #include "components/translate/core/browser/translate_client.h"
35 #include "components/translate/core/browser/translate_download_manager.h"
36 #include "components/translate/core/browser/translate_driver.h"
37 #include "components/translate/core/browser/translate_error_details.h"
38 #include "components/translate/core/browser/translate_init_details.h"
39 #include "components/translate/core/browser/translate_language_list.h"
40 #include "components/translate/core/browser/translate_metrics_logger.h"
41 #include "components/translate/core/browser/translate_metrics_logger_impl.h"
42 #include "components/translate/core/browser/translate_prefs.h"
43 #include "components/translate/core/browser/translate_ranker.h"
44 #include "components/translate/core/browser/translate_script.h"
45 #include "components/translate/core/browser/translate_trigger_decision.h"
46 #include "components/translate/core/browser/translate_url_util.h"
47 #include "components/translate/core/common/language_detection_details.h"
48 #include "components/translate/core/common/translate_constants.h"
49 #include "components/translate/core/common/translate_switches.h"
50 #include "components/variations/variations_associated_data.h"
51 #include "google_apis/google_api_keys.h"
52 #include "net/base/network_change_notifier.h"
53 #include "net/base/url_util.h"
54 #include "net/http/http_status_code.h"
55 #include "third_party/metrics_proto/translate_event.pb.h"
56 
57 namespace translate {
58 
59 namespace {
60 
61 // Callbacks for translate errors.
62 TranslateManager::TranslateErrorCallbackList* g_error_callback_list_ = nullptr;
63 
64 // Callbacks for translate initializations.
65 TranslateManager::TranslateInitCallbackList* g_init_callback_list_ = nullptr;
66 
67 const char kReportLanguageDetectionErrorURL[] =
68     "https://translate.google.com/translate_error?client=cr&action=langidc";
69 
70 // Used in kReportLanguageDetectionErrorURL to specify the original page
71 // language.
72 const char kSourceLanguageQueryName[] = "sl";
73 
74 // Used in kReportLanguageDetectionErrorURL to specify the page URL.
75 const char kUrlQueryName[] = "u";
76 
GetSkippedLanguagesForExperiments(std::string source_lang,translate::TranslatePrefs * translate_prefs)77 std::set<std::string> GetSkippedLanguagesForExperiments(
78     std::string source_lang,
79     translate::TranslatePrefs* translate_prefs) {
80   // Under this experiment, skip english as the target language if possible so
81   // that Translate triggers on English pages.
82   std::set<std::string> skipped_languages;
83   if (language::ShouldForceTriggerTranslateOnEnglishPages(
84           translate_prefs->GetForceTriggerOnEnglishPagesCount()) &&
85       source_lang == "en") {
86     skipped_languages.insert("en");
87   }
88   return skipped_languages;
89 }
90 
91 // Moves any element in |languages| for which |lang_code| is found in
92 // |skipped_languages| to the end of |languages|. Otherwise preserves relative
93 // ordering of elements. Modifies |languages| in place.
MoveSkippedLanguagesToEndIfNecessary(std::vector<std::string> * languages,const std::set<std::string> & skipped_languages)94 void MoveSkippedLanguagesToEndIfNecessary(
95     std::vector<std::string>* languages,
96     const std::set<std::string>& skipped_languages) {
97   if (!skipped_languages.empty()) {
98     std::stable_partition(
99         languages->begin(), languages->end(), [&](const auto& lang) {
100           return skipped_languages.find(lang) == skipped_languages.end();
101         });
102   }
103 }
104 
105 }  // namespace
106 
107 const base::Feature kOverrideLanguagePrefsForHrefTranslate{
108     "OverrideLanguagePrefsForHrefTranslate", base::FEATURE_ENABLED_BY_DEFAULT};
109 
110 const base::Feature kOverrideSitePrefsForHrefTranslate{
111     "OverrideSitePrefsForHrefTranslate", base::FEATURE_DISABLED_BY_DEFAULT};
112 
113 const char kForceAutoTranslateKey[] = "force-auto-translate";
114 
115 TranslateManager::~TranslateManager() = default;
116 
117 // static
118 std::unique_ptr<TranslateManager::TranslateErrorCallbackList::Subscription>
RegisterTranslateErrorCallback(const TranslateManager::TranslateErrorCallback & callback)119 TranslateManager::RegisterTranslateErrorCallback(
120     const TranslateManager::TranslateErrorCallback& callback) {
121   if (!g_error_callback_list_)
122     g_error_callback_list_ = new TranslateErrorCallbackList;
123   return g_error_callback_list_->Add(callback);
124 }
125 
126 // static
127 std::unique_ptr<TranslateManager::TranslateInitCallbackList::Subscription>
RegisterTranslateInitCallback(const TranslateManager::TranslateInitCallback & callback)128 TranslateManager::RegisterTranslateInitCallback(
129     const TranslateManager::TranslateInitCallback& callback) {
130   if (!g_init_callback_list_)
131     g_init_callback_list_ = new TranslateInitCallbackList;
132   return g_init_callback_list_->Add(callback);
133 }
134 
TranslateManager(TranslateClient * translate_client,TranslateRanker * translate_ranker,language::LanguageModel * language_model)135 TranslateManager::TranslateManager(TranslateClient* translate_client,
136                                    TranslateRanker* translate_ranker,
137                                    language::LanguageModel* language_model)
138     : page_seq_no_(0),
139       translate_client_(translate_client),
140       translate_driver_(translate_client_->GetTranslateDriver()),
141       translate_ranker_(translate_ranker),
142       language_model_(language_model),
143       null_translate_metrics_logger_(
144           std::make_unique<NullTranslateMetricsLogger>()),
145       language_state_(translate_driver_),
146       translate_event_(std::make_unique<metrics::TranslateEventProto>()) {}
147 
GetWeakPtr()148 base::WeakPtr<TranslateManager> TranslateManager::GetWeakPtr() {
149   return weak_method_factory_.GetWeakPtr();
150 }
151 
InitiateTranslation(const std::string & page_lang)152 void TranslateManager::InitiateTranslation(const std::string& page_lang) {
153   std::unique_ptr<TranslatePrefs> translate_prefs(
154       translate_client_->GetTranslatePrefs());
155   std::string page_language_code =
156       TranslateDownloadManager::GetLanguageCode(page_lang);
157   const std::set<std::string>& skipped_languages =
158       GetSkippedLanguagesForExperiments(page_language_code,
159                                         translate_prefs.get());
160   std::string target_lang = GetTargetLanguage(
161       translate_prefs.get(), language_model_, skipped_languages);
162 
163   // TODO(crbug.com/924980): The ranker event shouldn't be a global on this
164   // object. It should instead be passed around to code that uses it.
165   InitTranslateEvent(page_language_code, target_lang, *translate_prefs);
166 
167   const TranslateTriggerDecision& decision = ComputePossibleOutcomes(
168       translate_prefs.get(), page_language_code, target_lang);
169 
170   MaybeShowOmniboxIcon(decision);
171   bool ui_shown = MaterializeDecision(decision, translate_prefs.get(),
172                                       page_language_code, target_lang);
173 
174   NotifyTranslateInit(page_language_code, target_lang, decision, ui_shown);
175 
176   RecordDecisionMetrics(decision, page_language_code, ui_shown);
177   RecordDecisionRankerEvent(decision, translate_prefs.get(), page_language_code,
178                             target_lang);
179 
180   // Mark the current state as the initial state now that we are done
181   // initializing Translate.
182   GetActiveTranslateMetricsLogger()->LogInitialState();
183 }
184 
OnAutofillAssistantFinished()185 void TranslateManager::OnAutofillAssistantFinished() {
186   if (!page_language_code_.empty()) {
187     InitiateTranslation(page_language_code_);
188   }
189 }
190 
191 // static
GetManualTargetLanguage(const std::string & source_code,const LanguageState & language_state,translate::TranslatePrefs * prefs,language::LanguageModel * language_model)192 std::string TranslateManager::GetManualTargetLanguage(
193     const std::string& source_code,
194     const LanguageState& language_state,
195     translate::TranslatePrefs* prefs,
196     language::LanguageModel* language_model) {
197   if (language_state.IsPageTranslated()) {
198     return language_state.current_language();
199   } else {
200     const std::set<std::string>& skipped_languages =
201         GetSkippedLanguagesForExperiments(source_code, prefs);
202     return GetTargetLanguage(prefs, language_model, skipped_languages);
203   }
204 }
205 
CanManuallyTranslate(bool menuLogging)206 bool TranslateManager::CanManuallyTranslate(bool menuLogging) {
207   bool can_translate = true;
208 
209   if (!base::FeatureList::IsEnabled(translate::kTranslate)) {
210     if (!menuLogging)
211       return false;
212     TranslateBrowserMetrics::ReportMenuTranslationUnavailableReason(
213         TranslateBrowserMetrics::MenuTranslationUnavailableReason::
214             kTranslateDisabled);
215     can_translate = false;
216   }
217 
218   if (net::NetworkChangeNotifier::IsOffline()) {
219     if (!menuLogging)
220       return false;
221     TranslateBrowserMetrics::ReportMenuTranslationUnavailableReason(
222         TranslateBrowserMetrics::MenuTranslationUnavailableReason::
223             kNetworkOffline);
224     can_translate = false;
225   }
226 
227   if (!ignore_missing_key_for_testing_ &&
228       !::google_apis::HasAPIKeyConfigured()) {
229     if (!menuLogging)
230       return false;
231     TranslateBrowserMetrics::ReportMenuTranslationUnavailableReason(
232         TranslateBrowserMetrics::MenuTranslationUnavailableReason::
233             kApiKeysMissing);
234     can_translate = false;
235   }
236 
237   // MHTML pages currently cannot be translated (crbug.com/217945).
238   if (translate_driver_->GetContentsMimeType() == "multipart/related") {
239     if (!menuLogging)
240       return false;
241     TranslateBrowserMetrics::ReportMenuTranslationUnavailableReason(
242         TranslateBrowserMetrics::MenuTranslationUnavailableReason::kMHTMLPage);
243     can_translate = false;
244   }
245 
246   if (!translate_client_->IsTranslatableURL(
247           translate_driver_->GetVisibleURL())) {
248     if (!menuLogging)
249       return false;
250     TranslateBrowserMetrics::ReportMenuTranslationUnavailableReason(
251         TranslateBrowserMetrics::MenuTranslationUnavailableReason::
252             kURLNotTranslatable);
253     can_translate = false;
254   }
255 
256   const std::string source_language = language_state_.original_language();
257   if (source_language.empty()) {
258     if (!menuLogging)
259       return false;
260     TranslateBrowserMetrics::ReportMenuTranslationUnavailableReason(
261         TranslateBrowserMetrics::MenuTranslationUnavailableReason::
262             kSourceLangUnknown);
263     can_translate = false;
264   }
265   // Translation of unknown source language pages is supported on desktop
266   // platforms, but not mobile.
267 #if defined(OS_ANDROID) || defined(OS_IOS)
268   if (source_language == translate::kUnknownLanguageCode) {
269     if (!menuLogging)
270       return false;
271     TranslateBrowserMetrics::ReportMenuTranslationUnavailableReason(
272         TranslateBrowserMetrics::MenuTranslationUnavailableReason::
273             kSourceLangUnknown);
274     can_translate = false;
275   }
276 #endif
277 
278   std::unique_ptr<TranslatePrefs> translate_prefs(
279       translate_client_->GetTranslatePrefs());
280   if (!translate_prefs->IsTranslateAllowedByPolicy()) {
281     if (!menuLogging)
282       return false;
283     TranslateBrowserMetrics::ReportMenuTranslationUnavailableReason(
284         TranslateBrowserMetrics::MenuTranslationUnavailableReason::
285             kNotAllowedByPolicy);
286     can_translate = false;
287   }
288 
289   const std::string target_lang = GetManualTargetLanguage(
290       TranslateDownloadManager::GetLanguageCode(source_language),
291       language_state_, translate_prefs.get(), language_model_);
292   if (target_lang.empty()) {
293     if (!menuLogging)
294       return false;
295     TranslateBrowserMetrics::ReportMenuTranslationUnavailableReason(
296         TranslateBrowserMetrics::MenuTranslationUnavailableReason::
297             kTargetLangUnknown);
298     can_translate = false;
299   }
300 
301   if (menuLogging)
302     UMA_HISTOGRAM_BOOLEAN("Translate.MenuTranslation.IsAvailable",
303                           can_translate);
304 
305   return can_translate;
306 }
307 
InitiateManualTranslation(bool auto_translate,bool triggered_from_menu)308 void TranslateManager::InitiateManualTranslation(bool auto_translate,
309                                                  bool triggered_from_menu) {
310   // If a translation has already been triggered, do nothing.
311   if (language_state_.IsPageTranslated() ||
312       language_state_.translation_pending())
313     return;
314 
315   std::unique_ptr<TranslatePrefs> translate_prefs(
316       translate_client_->GetTranslatePrefs());
317   const std::string source_code = TranslateDownloadManager::GetLanguageCode(
318       language_state_.original_language());
319   const std::string target_lang = GetManualTargetLanguage(
320       source_code, language_state_, translate_prefs.get(), language_model_);
321 
322   language_state_.SetTranslateEnabled(true);
323 
324   // Translate the page if it has not been translated and manual translate
325   // should trigger translation automatically. Otherwise, only show the infobar.
326   if (auto_translate) {
327     TranslatePage(source_code, target_lang, triggered_from_menu);
328     return;
329   }
330 
331   const translate::TranslateStep step =
332       language_state_.IsPageTranslated()
333           ? translate::TRANSLATE_STEP_AFTER_TRANSLATE
334           : translate::TRANSLATE_STEP_BEFORE_TRANSLATE;
335   translate_client_->ShowTranslateUI(step, source_code, target_lang,
336                                      TranslateErrors::NONE,
337                                      true /* triggered_by_menu */);
338 }
339 
TranslatePage(const std::string & original_source_lang,const std::string & target_lang,bool triggered_from_menu)340 void TranslateManager::TranslatePage(const std::string& original_source_lang,
341                                      const std::string& target_lang,
342                                      bool triggered_from_menu) {
343   if (!translate_driver_->HasCurrentPage()) {
344     NOTREACHED();
345     return;
346   }
347 
348   // Log the source and target languages of the translate request.
349   TranslateBrowserMetrics::ReportTranslateSourceLanguage(original_source_lang);
350   TranslateBrowserMetrics::ReportTranslateTargetLanguage(target_lang);
351 
352   // If the source language matches the UI language, it means the translation
353   // prompt is being forced by an experiment. Report this so the count of how
354   // often it happens can be decremented (meaning the user didn't decline or
355   // ignore the prompt).
356   if (original_source_lang ==
357       TranslateDownloadManager::GetLanguageCode(
358           TranslateDownloadManager::GetInstance()->application_locale())) {
359     translate_client_->GetTranslatePrefs()
360         ->ReportAcceptedAfterForceTriggerOnEnglishPages();
361   }
362 
363   // If the target language isn't in the chrome://settings/languages list, add
364   // it there. This way, it's obvious to the user that Chrome is remembering
365   // their choice, they can remove it from the list, and they'll send that
366   // language in the Accept-Language header, giving servers a chance to serve
367   // them pages in that language.
368   AddTargetLanguageToAcceptLanguages(target_lang);
369 
370   // Translation can be kicked by context menu against unsupported languages.
371   // Unsupported language strings should be replaced with
372   // kUnknownLanguageCode in order to send a translation request with enabling
373   // server side auto language detection.
374   std::string source_lang(original_source_lang);
375   if (!TranslateDownloadManager::IsSupportedLanguage(source_lang))
376     source_lang = std::string(translate::kUnknownLanguageCode);
377 
378   // Capture the translate event if we were triggered from the menu.
379   if (triggered_from_menu) {
380     RecordTranslateEvent(
381         metrics::TranslateEventProto::USER_CONTEXT_MENU_TRANSLATE);
382   }
383 
384   if (source_lang == target_lang) {
385     // If the languages are the same, try the translation using the unknown
386     // language code on Desktop. Android and iOS don't support unknown source
387     // language, so this silently falls back to 'auto' when making the
388     // translation request. The source and target languages should only be equal
389     // if the translation was manually triggered by the user. Rather than show
390     // them the error, we should attempt to send the page for translation. For
391     // page with multiple languages we often detect same language, but the
392     // Translation service is able to translate the various languages using it's
393     // own language detection.
394 #if !defined(OS_ANDROID) && !defined(OS_IOS)
395     source_lang = translate::kUnknownLanguageCode;
396 #endif
397     TranslateBrowserMetrics::ReportInitiationStatus(
398         TranslateBrowserMetrics::
399             INITIATION_STATUS_IDENTICAL_LANGUAGE_USE_SOURCE_LANGUAGE_UNKNOWN);
400   }
401 
402   // Trigger the "translating now" UI.
403   translate_client_->ShowTranslateUI(
404       translate::TRANSLATE_STEP_TRANSLATING, source_lang, target_lang,
405       TranslateErrors::NONE, triggered_from_menu);
406 
407   GetActiveTranslateMetricsLogger()->LogTranslationStarted();
408 
409   TranslateScript* script = TranslateDownloadManager::GetInstance()->script();
410   DCHECK(script != nullptr);
411 
412   const std::string& script_data = script->data();
413   if (!script_data.empty()) {
414     DoTranslatePage(script_data, source_lang, target_lang);
415     return;
416   }
417 
418   // The script is not available yet.  Queue that request and query for the
419   // script.  Once it is downloaded we'll do the translate.
420   TranslateScript::RequestCallback callback =
421       base::BindOnce(&TranslateManager::OnTranslateScriptFetchComplete,
422                      GetWeakPtr(), source_lang, target_lang);
423 
424   script->Request(std::move(callback), translate_driver_->IsIncognito());
425 }
426 
RevertTranslation()427 void TranslateManager::RevertTranslation() {
428   // Capture the revert event in the translate metrics
429   RecordTranslateEvent(metrics::TranslateEventProto::USER_REVERT);
430 
431   // Revert the translation.
432   translate_driver_->RevertTranslation(page_seq_no_);
433   language_state_.SetCurrentLanguage(language_state_.original_language());
434 
435   GetActiveTranslateMetricsLogger()->LogReversion();
436 }
437 
ReportLanguageDetectionError()438 void TranslateManager::ReportLanguageDetectionError() {
439   TranslateBrowserMetrics::ReportLanguageDetectionError();
440 
441   GURL report_error_url = GURL(kReportLanguageDetectionErrorURL);
442 
443   report_error_url = net::AppendQueryParameter(
444       report_error_url, kUrlQueryName,
445       translate_driver_->GetLastCommittedURL().spec());
446 
447   report_error_url =
448       net::AppendQueryParameter(report_error_url, kSourceLanguageQueryName,
449                                 language_state_.original_language());
450 
451   report_error_url = translate::AddHostLocaleToUrl(report_error_url);
452   report_error_url = translate::AddApiKeyToUrl(report_error_url);
453 
454   translate_client_->ShowReportLanguageDetectionErrorUI(report_error_url);
455 }
456 
DoTranslatePage(const std::string & translate_script,const std::string & source_lang,const std::string & target_lang)457 void TranslateManager::DoTranslatePage(const std::string& translate_script,
458                                        const std::string& source_lang,
459                                        const std::string& target_lang) {
460   language_state_.set_translation_pending(true);
461   translate_driver_->TranslatePage(page_seq_no_, translate_script, source_lang,
462                                    target_lang);
463 }
464 
465 // Notifies |g_error_callback_list_| of translate errors.
NotifyTranslateError(TranslateErrors::Type error_type)466 void TranslateManager::NotifyTranslateError(TranslateErrors::Type error_type) {
467   if (!g_error_callback_list_ || error_type == TranslateErrors::NONE ||
468       translate_driver_->IsIncognito()) {
469     return;
470   }
471 
472   TranslateErrorDetails error_details;
473   error_details.time = base::Time::Now();
474   error_details.url = translate_driver_->GetLastCommittedURL();
475   error_details.error = error_type;
476   g_error_callback_list_->Notify(error_details);
477 }
478 
NotifyTranslateInit(std::string page_language_code,std::string target_lang,TranslateTriggerDecision decision,bool ui_shown)479 void TranslateManager::NotifyTranslateInit(std::string page_language_code,
480                                            std::string target_lang,
481                                            TranslateTriggerDecision decision,
482                                            bool ui_shown) {
483   if (!g_init_callback_list_ || translate_driver_->IsIncognito())
484     return;
485 
486   TranslateInitDetails details;
487   details.time = base::Time::Now();
488   details.url = translate_driver_->GetLastCommittedURL();
489   details.page_language_code = page_language_code;
490   details.target_lang = target_lang;
491   details.decision = decision;
492   details.ui_shown = ui_shown;
493 
494   g_init_callback_list_->Notify(details);
495 }
496 
PageTranslated(const std::string & source_lang,const std::string & target_lang,TranslateErrors::Type error_type)497 void TranslateManager::PageTranslated(const std::string& source_lang,
498                                       const std::string& target_lang,
499                                       TranslateErrors::Type error_type) {
500   if (error_type == TranslateErrors::NONE) {
501     // The user could have updated the source language before translating, so
502     // update the language state with both original and current.
503     language_state_.SetOriginalLanguage(source_lang);
504     language_state_.SetCurrentLanguage(target_lang);
505   }
506 
507   language_state_.set_translation_pending(false);
508   language_state_.set_translation_error(error_type != TranslateErrors::NONE);
509 
510   if ((error_type == TranslateErrors::NONE) &&
511       source_lang != translate::kUnknownLanguageCode &&
512       !TranslateDownloadManager::IsSupportedLanguage(source_lang)) {
513     error_type = TranslateErrors::UNSUPPORTED_LANGUAGE;
514   }
515 
516   // Currently we only want to log any error happens during the translation
517   // script initialization phase such as translation script failed because of
518   // CSP issues (crbug.com/738277).
519   // Note: NotifyTranslateError and ShowTranslateUI will not log the errors.
520   if (error_type == TranslateErrors::INITIALIZATION_ERROR)
521     RecordTranslateEvent(metrics::TranslateEventProto::INITIALIZATION_ERROR);
522   translate_client_->ShowTranslateUI(translate::TRANSLATE_STEP_AFTER_TRANSLATE,
523                                      source_lang, target_lang, error_type,
524                                      false);
525   NotifyTranslateError(error_type);
526 
527   GetActiveTranslateMetricsLogger()->LogTranslationFinished(
528       error_type == TranslateErrors::NONE);
529 }
530 
OnTranslateScriptFetchComplete(const std::string & source_lang,const std::string & target_lang,bool success,const std::string & data)531 void TranslateManager::OnTranslateScriptFetchComplete(
532     const std::string& source_lang,
533     const std::string& target_lang,
534     bool success,
535     const std::string& data) {
536   if (!translate_driver_->HasCurrentPage())
537     return;
538 
539   if (success) {
540     // Translate the page.
541     TranslateScript* translate_script =
542         TranslateDownloadManager::GetInstance()->script();
543     DCHECK(translate_script);
544     DoTranslatePage(translate_script->data(), source_lang, target_lang);
545   } else {
546     translate_client_->ShowTranslateUI(
547         translate::TRANSLATE_STEP_TRANSLATE_ERROR, source_lang, target_lang,
548         TranslateErrors::NETWORK, false);
549     NotifyTranslateError(TranslateErrors::NETWORK);
550     GetActiveTranslateMetricsLogger()->LogTranslationFinished(false);
551   }
552 }
553 
554 // static
GetTargetLanguage(const TranslatePrefs * prefs,language::LanguageModel * language_model,const std::set<std::string> & skipped_languages)555 std::string TranslateManager::GetTargetLanguage(
556     const TranslatePrefs* prefs,
557     language::LanguageModel* language_model,
558     const std::set<std::string>& skipped_languages) {
559   DCHECK(prefs);
560   const std::string& recent_target = prefs->GetRecentTargetLanguage();
561 
562   // If we've recorded the most recent target language, use that.
563   if (base::FeatureList::IsEnabled(kTranslateRecentTarget) &&
564       !recent_target.empty()) {
565     TranslateBrowserMetrics::ReportTranslateTargetLanguageOrigin(
566         TranslateBrowserMetrics::TargetLanguageOrigin::kRecentTarget);
567     return recent_target;
568   }
569 
570   if (language_model) {
571     std::vector<std::string> language_codes;
572     for (const auto& lang : language_model->GetLanguages()) {
573       std::string lang_code =
574           TranslateDownloadManager::GetLanguageCode(lang.lang_code);
575       language::ToTranslateLanguageSynonym(&lang_code);
576       if (TranslateDownloadManager::IsSupportedLanguage(lang_code))
577         language_codes.push_back(lang_code);
578     }
579     // If some languages need to be skipped, move them to the end of the
580     // language vector so that any other eligible language takes priority.
581     MoveSkippedLanguagesToEndIfNecessary(&language_codes, skipped_languages);
582 
583     // Use the first language from the model that translate supports.
584     if (!language_codes.empty()) {
585       TranslateBrowserMetrics::ReportTranslateTargetLanguageOrigin(
586           TranslateBrowserMetrics::TargetLanguageOrigin::kLanguageModel);
587       return language_codes[0];
588     }
589   }
590 
591   // Get the browser's user interface language.
592   std::string language = TranslateDownloadManager::GetLanguageCode(
593       TranslateDownloadManager::GetInstance()->application_locale());
594   // Map 'he', 'nb', 'fil' back to 'iw', 'no', 'tl'
595   language::ToTranslateLanguageSynonym(&language);
596   if (TranslateDownloadManager::IsSupportedLanguage(language)) {
597     TranslateBrowserMetrics::ReportTranslateTargetLanguageOrigin(
598         TranslateBrowserMetrics::TargetLanguageOrigin::kApplicationUI);
599     return language;
600   }
601 
602   // Will translate to the first supported language on the Accepted Language
603   // list or not at all if no such candidate exists.
604   std::vector<std::string> accept_languages_list;
605   prefs->GetLanguageList(&accept_languages_list);
606   for (const auto& lang : accept_languages_list) {
607     std::string lang_code = TranslateDownloadManager::GetLanguageCode(lang);
608     if (TranslateDownloadManager::IsSupportedLanguage(lang_code)) {
609       TranslateBrowserMetrics::ReportTranslateTargetLanguageOrigin(
610           TranslateBrowserMetrics::TargetLanguageOrigin::kAcceptLanguages);
611       return lang_code;
612     }
613   }
614 
615   // If there isn't a target language determined by the above logic, default to
616   // English. Otherwise the user can get stuck not being able to translate. See
617   // https://crbug.com/1041387.
618   TranslateBrowserMetrics::ReportTranslateTargetLanguageOrigin(
619       TranslateBrowserMetrics::TargetLanguageOrigin::kDefaultEnglish);
620   return std::string("en");
621 }
622 
623 // static
GetTargetLanguage(const TranslatePrefs * prefs,language::LanguageModel * language_model)624 std::string TranslateManager::GetTargetLanguage(
625     const TranslatePrefs* prefs,
626     language::LanguageModel* language_model) {
627   return GetTargetLanguage(prefs, language_model, {});
628 }
629 
630 // static
GetAutoTargetLanguage(const std::string & original_language,TranslatePrefs * translate_prefs)631 std::string TranslateManager::GetAutoTargetLanguage(
632     const std::string& original_language,
633     TranslatePrefs* translate_prefs) {
634   std::string auto_target_lang;
635   if (translate_prefs->ShouldAutoTranslate(original_language,
636                                            &auto_target_lang)) {
637     // We need to confirm that the saved target language is still supported.
638     // Also, GetLanguageCode will take care of removing country code if any.
639     auto_target_lang =
640         TranslateDownloadManager::GetLanguageCode(auto_target_lang);
641     if (TranslateDownloadManager::IsSupportedLanguage(auto_target_lang))
642       return auto_target_lang;
643   }
644   return std::string();
645 }
646 
GetLanguageState()647 LanguageState* TranslateManager::GetLanguageState() {
648   return &language_state_;
649 }
650 
651 bool TranslateManager::ignore_missing_key_for_testing_ = false;
652 
653 // static
SetIgnoreMissingKeyForTesting(bool ignore)654 void TranslateManager::SetIgnoreMissingKeyForTesting(bool ignore) {
655   ignore_missing_key_for_testing_ = ignore;
656 }
657 
658 // static
IsAvailable(const TranslatePrefs * prefs)659 bool TranslateManager::IsAvailable(const TranslatePrefs* prefs) {
660   // These conditions mirror the conditions in InitiateTranslation.
661   return base::FeatureList::IsEnabled(translate::kTranslate) &&
662          (ignore_missing_key_for_testing_ ||
663           ::google_apis::HasAPIKeyConfigured()) &&
664          prefs->IsOfferTranslateEnabled();
665 }
666 
InitTranslateEvent(const std::string & src_lang,const std::string & dst_lang,const TranslatePrefs & prefs)667 void TranslateManager::InitTranslateEvent(const std::string& src_lang,
668                                           const std::string& dst_lang,
669                                           const TranslatePrefs& prefs) {
670   translate_event_->Clear();
671   translate_event_->set_source_language(src_lang);
672   translate_event_->set_target_language(dst_lang);
673   translate_event_->set_country(prefs.GetCountry());
674   translate_event_->set_accept_count(
675       prefs.GetTranslationAcceptedCount(src_lang));
676   translate_event_->set_decline_count(
677       prefs.GetTranslationDeniedCount(src_lang));
678   translate_event_->set_ignore_count(
679       prefs.GetTranslationIgnoredCount(src_lang));
680   translate_event_->set_ranker_response(
681       metrics::TranslateEventProto::NOT_QUERIED);
682   translate_event_->set_event_type(metrics::TranslateEventProto::UNKNOWN);
683   // TODO(rogerm): Populate the language list.
684 }
685 
RecordTranslateEvent(int event_type)686 void TranslateManager::RecordTranslateEvent(int event_type) {
687   translate_ranker_->RecordTranslateEvent(
688       event_type, translate_driver_->GetUkmSourceId(), translate_event_.get());
689 }
690 
ShouldOverrideDecision(int event_type)691 bool TranslateManager::ShouldOverrideDecision(int event_type) {
692   return translate_ranker_->ShouldOverrideDecision(
693       event_type, translate_driver_->GetUkmSourceId(), translate_event_.get());
694 }
695 
ShouldSuppressBubbleUI(bool triggered_from_menu,const std::string & source_language)696 bool TranslateManager::ShouldSuppressBubbleUI(
697     bool triggered_from_menu,
698     const std::string& source_language) {
699   // Suppress the UI if the user navigates to a page with
700   // the same language as the previous page. In the new UI,
701   // continue offering translation after the user navigates
702   // to another page.
703   if (!language_state_.HasLanguageChanged() &&
704       !ShouldOverrideDecision(
705           metrics::TranslateEventProto::MATCHES_PREVIOUS_LANGUAGE)) {
706     TranslateBrowserMetrics::ReportInitiationStatus(
707         TranslateBrowserMetrics::
708             INITIATION_STATUS_ABORTED_BY_MATCHES_PREVIOUS_LANGUAGE);
709     return true;
710   }
711 
712   // Suppress the UI if the user denied translation for this language
713   // too often.
714   if (!triggered_from_menu &&
715       translate_client_->GetTranslatePrefs()->IsTooOftenDenied(
716           source_language) &&
717       !ShouldOverrideDecision(
718           metrics::TranslateEventProto::LANGUAGE_DISABLED_BY_AUTO_BLACKLIST)) {
719     TranslateBrowserMetrics::ReportInitiationStatus(
720         TranslateBrowserMetrics::INITIATION_STATUS_ABORTED_BY_TOO_OFTEN_DENIED);
721     return true;
722   }
723 
724   return false;
725 }
726 
AddTargetLanguageToAcceptLanguages(const std::string & target_language_code)727 void TranslateManager::AddTargetLanguageToAcceptLanguages(
728     const std::string& target_language_code) {
729   auto prefs = translate_client_->GetTranslatePrefs();
730   std::vector<std::string> languages;
731   prefs->GetLanguageList(&languages);
732 
733   base::StringPiece target_language, tail;
734   // |target_language_code| should satisfy BCP47 and consist of a language code
735   // and an optional region code joined by an hyphen.
736   std::tie(target_language, tail) =
737       language::SplitIntoMainAndTail(target_language_code);
738 
739   // Don't add the target language if it's redundant with another already in the
740   // list.
741   if (tail.empty()) {
742     for (const auto& language : languages) {
743       if (language::ExtractBaseLanguage(language) == target_language)
744         return;
745     }
746   } else {
747     for (const auto& language : languages) {
748       if (language == target_language_code)
749         return;
750     }
751   }
752 
753   // Only add the target language if it's not an automatic target (such as when
754   // translation happens because of an hrefTranslate navigation).
755   if (language_state_.AutoTranslateTo() != target_language_code &&
756       language_state_.href_translate() != target_language_code) {
757     prefs->AddToLanguageList(target_language_code, /*force_blocked=*/false);
758   }
759 }
760 
ComputePossibleOutcomes(TranslatePrefs * translate_prefs,const std::string & page_language_code,const std::string & target_lang)761 const TranslateTriggerDecision TranslateManager::ComputePossibleOutcomes(
762     TranslatePrefs* translate_prefs,
763     const std::string& page_language_code,
764     const std::string& target_lang) {
765   // This function looks at a bunch of signals and determines which of three
766   // outcomes should be selected:
767   // 1. Auto-translate the page
768   // 2. Show translate UI
769   // 3. Do nothing
770   // This is achieved by passing the |decision| object to the different Filter*
771   // functions, which will mark certain outcomes as undesirable. This |decision|
772   // object is then used to trigger the correct behavior, and finally record
773   // corresponding metrics in InitiateTranslation.
774   TranslateTriggerDecision decision;
775 
776   FilterIsTranslatePossible(&decision, translate_prefs, page_language_code,
777                             target_lang);
778 
779   // Querying the ranker now, but not exiting immediately so that we may log
780   // other potential suppression reasons.
781   // Ignore Ranker's decision under triggering experiments since it wasn't
782   // trained appropriately under those scenarios.
783   if (!language::ShouldPreventRankerEnforcementInIndia(
784           translate_prefs->GetForceTriggerOnEnglishPagesCount()) &&
785       !translate_ranker_->ShouldOfferTranslation(
786           translate_event_.get(), GetActiveTranslateMetricsLogger())) {
787     decision.SuppressFromRanker();
788   }
789 
790   FilterForUserPrefs(&decision, translate_prefs, page_language_code);
791 
792   if (decision.should_suppress_from_ranker()) {
793     // Delay logging this until after FilterForUserPrefs because TriggerDecision
794     // values from FilterForUserPrefs have higher priority.
795     GetActiveTranslateMetricsLogger()->LogTriggerDecision(
796         TriggerDecision::kDisabledByRanker);
797   }
798 
799   FilterAutoTranslate(&decision, translate_prefs, page_language_code);
800   FilterForHrefTranslate(&decision, translate_prefs, page_language_code);
801   FilterForPredefinedTarget(&decision, translate_prefs, page_language_code);
802 
803   return decision;
804 }
805 
FilterIsTranslatePossible(TranslateTriggerDecision * decision,TranslatePrefs * translate_prefs,const std::string & page_language_code,const std::string & target_lang)806 void TranslateManager::FilterIsTranslatePossible(
807     TranslateTriggerDecision* decision,
808     TranslatePrefs* translate_prefs,
809     const std::string& page_language_code,
810     const std::string& target_lang) {
811   // Short-circuit out if not in a state where initiating translation makes
812   // sense (this method may be called multiple times for a given page).
813   if (!language_state_.page_needs_translation() ||
814       language_state_.translation_pending() ||
815       language_state_.translation_declined() ||
816       language_state_.IsPageTranslated()) {
817     decision->PreventAllTriggering();
818     decision->initiation_statuses.push_back(
819         TranslateBrowserMetrics::INITIATION_STATUS_DOESNT_NEED_TRANSLATION);
820     GetActiveTranslateMetricsLogger()->LogTriggerDecision(
821         TriggerDecision::kDisabledDoesntNeedTranslation);
822   }
823 
824   if (!base::FeatureList::IsEnabled(translate::kTranslate)) {
825     decision->PreventAllTriggering();
826     decision->initiation_statuses.push_back(
827         TranslateBrowserMetrics::INITIATION_STATUS_DISABLED_BY_SWITCH);
828     GetActiveTranslateMetricsLogger()->LogTriggerDecision(
829         TriggerDecision::kDisabledTranslationFeatureDisabled);
830   }
831 
832   // Also, skip if the connection is currently offline - initiation doesn't make
833   // sense there, either.
834   if (net::NetworkChangeNotifier::IsOffline()) {
835     decision->PreventAllTriggering();
836     decision->initiation_statuses.push_back(
837         TranslateBrowserMetrics::INITIATION_STATUS_NO_NETWORK);
838     GetActiveTranslateMetricsLogger()->LogTriggerDecision(
839         TriggerDecision::kDisabledOffline);
840   }
841 
842   // Skip translation if autofill assistant is running.
843   if (translate_client_->IsAutofillAssistantRunning()) {
844     page_language_code_ = page_language_code;
845     decision->PreventAllTriggering();
846     decision->initiation_statuses.push_back(
847         TranslateBrowserMetrics::
848             INITIATION_STATUS_DISABLED_BY_AUTOFILL_ASSISTANT);
849     GetActiveTranslateMetricsLogger()
850         ->LogAutofillAssistantDeferredTriggerDecision();
851   }
852 
853   if (!ignore_missing_key_for_testing_ &&
854       !::google_apis::HasAPIKeyConfigured()) {
855     // Without an API key, translate won't work, so don't offer to translate in
856     // the first place. Leave prefs::kOfferTranslateEnabled on, though, because
857     // that settings syncs and we don't want to turn off translate everywhere
858     // else.
859     decision->PreventAllTriggering();
860     decision->initiation_statuses.push_back(
861         TranslateBrowserMetrics::INITIATION_STATUS_DISABLED_BY_KEY);
862     GetActiveTranslateMetricsLogger()->LogTriggerDecision(
863         TriggerDecision::kDisabledMissingAPIKey);
864   }
865 
866   // MHTML pages currently cannot be translated.
867   // See bug: 217945.
868   if (translate_driver_->GetContentsMimeType() == "multipart/related") {
869     decision->PreventAllTriggering();
870     decision->initiation_statuses.push_back(
871         TranslateBrowserMetrics::INITIATION_STATUS_MIME_TYPE_IS_NOT_SUPPORTED);
872     GetActiveTranslateMetricsLogger()->LogTriggerDecision(
873         TriggerDecision::kDisabledMIMETypeNotSupported);
874   }
875 
876   // Don't translate any Chrome specific page, e.g., New Tab Page, Download,
877   // History, and so on.
878   const GURL& page_url = translate_driver_->GetVisibleURL();
879   if (!translate_client_->IsTranslatableURL(page_url)) {
880     decision->PreventAllTriggering();
881     decision->initiation_statuses.push_back(
882         TranslateBrowserMetrics::INITIATION_STATUS_URL_IS_NOT_SUPPORTED);
883     GetActiveTranslateMetricsLogger()->LogTriggerDecision(
884         TriggerDecision::kDisabledURLNotSupported);
885   }
886 
887   if (!translate_prefs->IsOfferTranslateEnabled()) {
888     decision->PreventAllTriggering();
889     decision->initiation_statuses.push_back(
890         TranslateBrowserMetrics::INITIATION_STATUS_DISABLED_BY_PREFS);
891     decision->ranker_events.push_back(
892         metrics::TranslateEventProto::DISABLED_BY_PREF);
893     GetActiveTranslateMetricsLogger()->LogTriggerDecision(
894         TriggerDecision::kDisabledNeverOfferTranslations);
895   }
896 
897   // Don't translate similar languages (ex: en-US to en).
898   if (page_language_code == target_lang) {
899     // This doesn't prevent *all* possible translate outcomes because some could
900     // use a different target language, making this condition only relevant to
901     // regular auto-translate/show UI.
902     decision->PreventAutoTranslate();
903     decision->PreventShowingUI();
904     decision->initiation_statuses.push_back(
905         TranslateBrowserMetrics::INITIATION_STATUS_SIMILAR_LANGUAGES);
906     GetActiveTranslateMetricsLogger()->LogTriggerDecision(
907         TriggerDecision::kDisabledSimilarLanguages);
908   }
909 
910   // Nothing to do if either the language Chrome is in or the language of
911   // the page is not supported by the translation server.
912   if (target_lang.empty() ||
913       !TranslateDownloadManager::IsSupportedLanguage(page_language_code)) {
914     // This doesn't prevent *all* possible translate outcomes because some could
915     // use a different target language, making this condition only relevant to
916     // regular auto-translate/show UI.
917     decision->PreventAutoTranslate();
918     decision->PreventShowingUI();
919     decision->initiation_statuses.push_back(
920         TranslateBrowserMetrics::INITIATION_STATUS_LANGUAGE_IS_NOT_SUPPORTED);
921     decision->ranker_events.push_back(
922         metrics::TranslateEventProto::UNSUPPORTED_LANGUAGE);
923     GetActiveTranslateMetricsLogger()->LogTriggerDecision(
924         TriggerDecision::kDisabledUnsupportedLanguage);
925   }
926 }
927 
FilterAutoTranslate(TranslateTriggerDecision * decision,TranslatePrefs * translate_prefs,const std::string & page_language_code)928 void TranslateManager::FilterAutoTranslate(
929     TranslateTriggerDecision* decision,
930     TranslatePrefs* translate_prefs,
931     const std::string& page_language_code) {
932   // Determine whether auto-translate is required, and if so for which target
933   // language.
934   std::string always_translate_target =
935       GetAutoTargetLanguage(page_language_code, translate_prefs);
936   std::string link_auto_translate_target = language_state_.AutoTranslateTo();
937   if (!translate_driver_->IsIncognito() && !always_translate_target.empty()) {
938     // If the user has previously selected "always translate" for this language
939     // we automatically translate.  Note that in incognito mode we disable that
940     // feature; the user will get an infobar, so they can control whether the
941     // page's text is sent to the translate server.
942     decision->auto_translate_target = always_translate_target;
943     decision->initiation_statuses.push_back(
944         TranslateBrowserMetrics::INITIATION_STATUS_AUTO_BY_CONFIG);
945     decision->ranker_events.push_back(
946         metrics::TranslateEventProto::AUTO_TRANSLATION_BY_PREF);
947     GetActiveTranslateMetricsLogger()->LogTriggerDecision(
948         TriggerDecision::kAutomaticTranslationByPref);
949   } else if (!link_auto_translate_target.empty()) {
950     // This page was navigated through a click from a translated page.
951     decision->auto_translate_target = link_auto_translate_target;
952     decision->initiation_statuses.push_back(
953         TranslateBrowserMetrics::INITIATION_STATUS_AUTO_BY_LINK);
954     decision->ranker_events.push_back(
955         metrics::TranslateEventProto::AUTO_TRANSLATION_BY_LINK);
956     GetActiveTranslateMetricsLogger()->LogTriggerDecision(
957         TriggerDecision::kAutomaticTranslationByLink);
958   }
959 
960   if (decision->auto_translate_target.empty()) {
961     decision->PreventAutoTranslate();
962   }
963 }
964 
FilterForUserPrefs(TranslateTriggerDecision * decision,TranslatePrefs * translate_prefs,const std::string & page_language_code)965 void TranslateManager::FilterForUserPrefs(
966     TranslateTriggerDecision* decision,
967     TranslatePrefs* translate_prefs,
968     const std::string& page_language_code) {
969   TranslateAcceptLanguages* accept_languages =
970       translate_client_->GetTranslateAcceptLanguages();
971   // Don't translate any user black-listed languages.
972   if (!translate_prefs->CanTranslateLanguage(accept_languages,
973                                              page_language_code)) {
974     decision->SetIsInLanguageBlocklist();
975 
976     decision->PreventAutoTranslate();
977     decision->PreventShowingUI();
978     decision->PreventShowingPredefinedLanguageTranslateUI();
979 
980     // Disable showing the translate UI for hrefTranslate unless hrefTranslate
981     // is supposed to override the language blocklist.
982     if (!base::FeatureList::IsEnabled(kOverrideLanguagePrefsForHrefTranslate)) {
983       decision->PreventShowingHrefTranslateUI();
984     }
985     // Disable auto-translating the page for hrefTranslate unless hrefTranslate
986     // is supposed to override the language blocklist for auto-translation as
987     // well. This is enabled by default, but the below if-statement also
988     // explicitly checks if the underlying base::Feature is enabled as well so
989     // that disabling the underlying base::Feature will also turn off forcing
990     // auto translation.
991     if (!base::FeatureList::IsEnabled(kOverrideLanguagePrefsForHrefTranslate) ||
992         !base::GetFieldTrialParamByFeatureAsBool(
993             kOverrideLanguagePrefsForHrefTranslate, kForceAutoTranslateKey,
994             true)) {
995       decision->PreventAutoHrefTranslate();
996     }
997 
998     decision->initiation_statuses.push_back(
999         TranslateBrowserMetrics::INITIATION_STATUS_DISABLED_BY_CONFIG);
1000     decision->ranker_events.push_back(
1001         metrics::TranslateEventProto::LANGUAGE_DISABLED_BY_USER_CONFIG);
1002     GetActiveTranslateMetricsLogger()->LogTriggerDecision(
1003         TriggerDecision::kDisabledNeverTranslateLanguage);
1004   }
1005 
1006   // Don't translate any user black-listed URLs.
1007   const GURL& page_url = translate_driver_->GetVisibleURL();
1008   if (translate_prefs->IsSiteBlacklisted(page_url.HostNoBrackets())) {
1009     decision->SetIsInSiteBlocklist();
1010 
1011     decision->PreventAutoTranslate();
1012     decision->PreventShowingUI();
1013     decision->PreventShowingPredefinedLanguageTranslateUI();
1014 
1015     // Disable showing the translate UI for hrefTranslate unless hrefTranslate
1016     // is supposed to override the site blocklist.
1017     if (!base::FeatureList::IsEnabled(kOverrideSitePrefsForHrefTranslate)) {
1018       decision->PreventShowingHrefTranslateUI();
1019     }
1020     // Disable auto-translating the page for hrefTranslate unless hrefTranslate
1021     // is supposed to override the site blocklist for auto-translation as well.
1022     if (!base::GetFieldTrialParamByFeatureAsBool(
1023             kOverrideSitePrefsForHrefTranslate, kForceAutoTranslateKey,
1024             false)) {
1025       decision->PreventAutoHrefTranslate();
1026     }
1027 
1028     decision->initiation_statuses.push_back(
1029         TranslateBrowserMetrics::INITIATION_STATUS_DISABLED_BY_CONFIG);
1030     decision->ranker_events.push_back(
1031         metrics::TranslateEventProto::URL_DISABLED_BY_USER_CONFIG);
1032     GetActiveTranslateMetricsLogger()->LogTriggerDecision(
1033         TriggerDecision::kDisabledNeverTranslateSite);
1034   }
1035 }
1036 
FilterForHrefTranslate(TranslateTriggerDecision * decision,TranslatePrefs * translate_prefs,const std::string & page_language_code)1037 void TranslateManager::FilterForHrefTranslate(
1038     TranslateTriggerDecision* decision,
1039     TranslatePrefs* translate_prefs,
1040     const std::string& page_language_code) {
1041   if (!language_state_.navigation_from_google()) {
1042     decision->PreventAutoHrefTranslate();
1043   }
1044 
1045   decision->href_translate_target = language_state_.href_translate();
1046   // Can't honor hrefTranslate if there's no specified target, the source or
1047   // the target aren't supported, or the source and target match.
1048   if (!IsTranslatableLanguagePair(page_language_code,
1049                                   decision->href_translate_target)) {
1050     decision->PreventAutoHrefTranslate();
1051     decision->PreventShowingHrefTranslateUI();
1052   }
1053 }
1054 
FilterForPredefinedTarget(TranslateTriggerDecision * decision,TranslatePrefs * translate_prefs,const std::string & page_language_code)1055 void TranslateManager::FilterForPredefinedTarget(
1056     TranslateTriggerDecision* decision,
1057     TranslatePrefs* translate_prefs,
1058     const std::string& page_language_code) {
1059   decision->predefined_translate_target =
1060       language_state_.GetPredefinedTargetLanguage();
1061   if (!IsTranslatableLanguagePair(page_language_code,
1062                                   decision->predefined_translate_target)) {
1063     decision->PreventShowingPredefinedLanguageTranslateUI();
1064   }
1065 }
1066 
IsTranslatableLanguagePair(const std::string & page_language_code,const std::string & target_language_code)1067 bool TranslateManager::IsTranslatableLanguagePair(
1068     const std::string& page_language_code,
1069     const std::string& target_language_code) {
1070   translate::TranslateLanguageList* language_list =
1071       translate::TranslateDownloadManager::GetInstance()->language_list();
1072 
1073   return !target_language_code.empty() &&
1074          language_list->IsSupportedLanguage(target_language_code) &&
1075          TranslateDownloadManager::IsSupportedLanguage(page_language_code) &&
1076          page_language_code != target_language_code;
1077 }
1078 
MaybeShowOmniboxIcon(const TranslateTriggerDecision & decision)1079 void TranslateManager::MaybeShowOmniboxIcon(
1080     const TranslateTriggerDecision& decision) {
1081   if (decision.IsTriggeringPossible()) {
1082     // Show the omnibox icon if any translate trigger is possible.
1083     language_state_.SetTranslateEnabled(true);
1084     TranslateBrowserMetrics::ReportInitiationStatus(
1085         TranslateBrowserMetrics::INITIATION_STATUS_SHOW_ICON);
1086   }
1087 }
1088 
MaterializeDecision(const TranslateTriggerDecision & decision,TranslatePrefs * translate_prefs,const std::string & page_language_code,const std::string target_lang)1089 bool TranslateManager::MaterializeDecision(
1090     const TranslateTriggerDecision& decision,
1091     TranslatePrefs* translate_prefs,
1092     const std::string& page_language_code,
1093     const std::string target_lang) {
1094   // Auto-translating always happens if it's still possible here.
1095   if (decision.can_auto_translate()) {
1096     TranslatePage(page_language_code, decision.auto_translate_target, false);
1097     return false;
1098   }
1099 
1100   if (decision.can_auto_href_translate()) {
1101     TranslatePage(page_language_code, decision.href_translate_target, false);
1102     return false;
1103   }
1104 
1105   // Auto-translate didn't happen, so check if the UI should be shown. It must
1106   // not be suppressed by preference, system state, or the Ranker.
1107 
1108   // Will be true if we've decided to show the infobar/bubble UI to the user.
1109   bool did_show_ui = false;
1110 
1111   // Check whether the target language is predefined. If it is predefined
1112   // trigger Translate UI even if it would not otherwise be triggered
1113   // or would be triggered with another language.
1114   if (decision.can_show_predefined_language_translate_ui()) {
1115     did_show_ui = translate_client_->ShowTranslateUI(
1116         translate::TRANSLATE_STEP_BEFORE_TRANSLATE, page_language_code,
1117         decision.predefined_translate_target, TranslateErrors::NONE, false);
1118   }
1119 
1120   if (!did_show_ui && decision.ShouldShowUI()) {
1121     // If the source language matches the UI language, it means the translation
1122     // prompt is being forced by an experiment. Report this so the count of how
1123     // often it happens can be tracked to suppress the experiment as necessary.
1124     if (page_language_code ==
1125         TranslateDownloadManager::GetLanguageCode(
1126             TranslateDownloadManager::GetInstance()->application_locale())) {
1127       translate_prefs->ReportForceTriggerOnEnglishPages();
1128     }
1129 
1130     // Prompts the user if they want the page translated.
1131     did_show_ui = translate_client_->ShowTranslateUI(
1132         translate::TRANSLATE_STEP_BEFORE_TRANSLATE, page_language_code,
1133         target_lang, TranslateErrors::NONE, false);
1134   }
1135 
1136   // Auto-translate didn't happen, and the UI wasn't shown so consider the
1137   // hrefTranslate attribute if it was present on the originating link.
1138   if (!did_show_ui && decision.can_show_href_translate_ui()) {
1139     did_show_ui = translate_client_->ShowTranslateUI(
1140         translate::TRANSLATE_STEP_BEFORE_TRANSLATE, page_language_code,
1141         decision.href_translate_target, TranslateErrors::NONE, false);
1142   }
1143 
1144   if (did_show_ui)
1145     GetActiveTranslateMetricsLogger()->LogTriggerDecision(
1146         TriggerDecision::kShowUI);
1147 
1148   return did_show_ui;
1149 }
1150 
RecordDecisionMetrics(const TranslateTriggerDecision & decision,const std::string & page_language_code,bool ui_shown)1151 void TranslateManager::RecordDecisionMetrics(
1152     const TranslateTriggerDecision& decision,
1153     const std::string& page_language_code,
1154     bool ui_shown) {
1155   // For Google navigations, the hrefTranslate hint may trigger a translation
1156   // automatically. Record metrics if there is navigation from Google and a
1157   // |decision.href_translate_target|.
1158   if (language_state_.navigation_from_google() &&
1159       !decision.href_translate_target.empty()) {
1160     if (decision.can_auto_translate() || decision.can_auto_href_translate()) {
1161       if (decision.can_auto_translate() &&
1162           decision.auto_translate_target != decision.href_translate_target) {
1163         TranslateBrowserMetrics::ReportTranslateHrefHintStatus(
1164             TranslateBrowserMetrics::HrefTranslateStatus::
1165                 kAutoTranslatedDifferentTargetLanguage);
1166       } else {
1167         TranslateBrowserMetrics::ReportTranslateHrefHintStatus(
1168             TranslateBrowserMetrics::HrefTranslateStatus::kAutoTranslated);
1169       }
1170     } else if (decision.can_show_href_translate_ui()) {
1171       TranslateBrowserMetrics::ReportTranslateHrefHintStatus(
1172           TranslateBrowserMetrics::HrefTranslateStatus::
1173               kUiShownNotAutoTranslated);
1174     } else {
1175       TranslateBrowserMetrics::ReportTranslateHrefHintStatus(
1176           TranslateBrowserMetrics::HrefTranslateStatus::
1177               kNoUiShownNotAutoTranslated);
1178     }
1179 
1180     if (decision.is_in_language_blocklist()) {
1181       if (decision.is_in_site_blocklist()) {
1182         TranslateBrowserMetrics::ReportTranslateHrefHintPrefsFilterStatus(
1183             TranslateBrowserMetrics::HrefTranslatePrefsFilterStatus::
1184                 kBothLanguageAndSiteInBlocklist);
1185       } else {
1186         TranslateBrowserMetrics::ReportTranslateHrefHintPrefsFilterStatus(
1187             TranslateBrowserMetrics::HrefTranslatePrefsFilterStatus::
1188                 kLanguageInBlocklist);
1189       }
1190     } else if (decision.is_in_site_blocklist()) {
1191       TranslateBrowserMetrics::ReportTranslateHrefHintPrefsFilterStatus(
1192           TranslateBrowserMetrics::HrefTranslatePrefsFilterStatus::
1193               kSiteInBlocklist);
1194     } else {
1195       TranslateBrowserMetrics::ReportTranslateHrefHintPrefsFilterStatus(
1196           TranslateBrowserMetrics::HrefTranslatePrefsFilterStatus::
1197               kNotInBlocklists);
1198     }
1199   }
1200 
1201   if (!decision.can_auto_translate() &&
1202       decision.can_show_predefined_language_translate_ui()) {
1203     TranslateBrowserMetrics::ReportInitiationStatus(
1204         TranslateBrowserMetrics::
1205             INITIATION_STATUS_SHOW_UI_PREDEFINED_TARGET_LANGUAGE);
1206 
1207     return;
1208   }
1209 
1210   // If the chosen outcome is to show the UI or let it be suppressed, log a few
1211   // explicit things.
1212   if (!decision.can_auto_translate() && decision.can_show_ui()) {
1213     // By getting here it's expected that nothing caused the translation to
1214     // be aborted or happen automatically. Because of that,
1215     // |decision.initiation_statuses| should be empty.
1216     DCHECK(decision.initiation_statuses.empty());
1217 
1218     if (decision.should_suppress_from_ranker() || !ui_shown) {
1219       TranslateBrowserMetrics::ReportInitiationStatus(
1220           TranslateBrowserMetrics::INITIATION_STATUS_SUPPRESS_INFOBAR);
1221     }
1222 
1223     // If the UI was suppressed, log the suppression source.
1224     if (decision.should_suppress_from_ranker()) {
1225       TranslateBrowserMetrics::ReportInitiationStatus(
1226           TranslateBrowserMetrics::INITIATION_STATUS_ABORTED_BY_RANKER);
1227     } else {
1228       // Always log INITIATION_STATUS_SHOW_INFOBAR regardless of whether it's
1229       // being subsequently suppressed or not. It's a measure of how often a
1230       // decision is taken to show it, and other metrics track *actual*
1231       // instances of it being shown.
1232       TranslateBrowserMetrics::ReportInitiationStatus(
1233           TranslateBrowserMetrics::INITIATION_STATUS_SHOW_INFOBAR);
1234     }
1235 
1236     // There's nothing else to log if the UI was shown.
1237     return;
1238   }
1239 
1240   // To match previous behavior, this function will log the first initiation
1241   // status that was recorded in the vector. This ensures that the histograms
1242   // reflect which conditions were met first to either trigger or prevent
1243   // translate triggering.
1244   if (!decision.initiation_statuses.empty()) {
1245     auto status = decision.initiation_statuses[0];
1246     if (status !=
1247         TranslateBrowserMetrics::INITIATION_STATUS_DOESNT_NEED_TRANSLATION) {
1248       // Don't record INITIATION_STATUS_DOESNT_NEED_TRANSLATION because it's
1249       // very frequent and not important to track.
1250       TranslateBrowserMetrics::ReportInitiationStatus(status);
1251     }
1252 
1253     // The following metrics are logged alongside extra info.
1254     if (status ==
1255         TranslateBrowserMetrics::INITIATION_STATUS_LANGUAGE_IS_NOT_SUPPORTED) {
1256       TranslateBrowserMetrics::ReportUnsupportedLanguageAtInitiation(
1257           page_language_code);
1258     }
1259 
1260     if (status ==
1261         TranslateBrowserMetrics::INITIATION_STATUS_DISABLED_BY_PREFS) {
1262       const std::string& locale =
1263           TranslateDownloadManager::GetInstance()->application_locale();
1264       TranslateBrowserMetrics::ReportLocalesOnDisabledByPrefs(locale);
1265     }
1266   }
1267 }
1268 
RecordDecisionRankerEvent(const TranslateTriggerDecision & decision,TranslatePrefs * translate_prefs,const std::string & page_language_code,const std::string & target_lang)1269 void TranslateManager::RecordDecisionRankerEvent(
1270     const TranslateTriggerDecision& decision,
1271     TranslatePrefs* translate_prefs,
1272     const std::string& page_language_code,
1273     const std::string& target_lang) {
1274   if (!decision.auto_translate_target.empty()) {
1275     translate_event_->set_modified_target_language(
1276         decision.auto_translate_target);
1277   }
1278 
1279   if (!decision.ranker_events.empty()) {
1280     auto event = decision.ranker_events[0];
1281     RecordTranslateEvent(event);
1282   }
1283 
1284   // Finally, if the decision was to show UI and ranker suppressed it, log that.
1285   if (!decision.can_auto_translate() && decision.can_show_ui() &&
1286       decision.should_suppress_from_ranker()) {
1287     RecordTranslateEvent(metrics::TranslateEventProto::DISABLED_BY_RANKER);
1288   }
1289 }
1290 
SetPredefinedTargetLanguage(const std::string & language_code)1291 void TranslateManager::SetPredefinedTargetLanguage(
1292     const std::string& language_code) {
1293   language_state_.SetPredefinedTargetLanguage(language_code);
1294 }
1295 
GetActiveTranslateMetricsLogger()1296 TranslateMetricsLogger* TranslateManager::GetActiveTranslateMetricsLogger() {
1297   // If |active_translate_metrics_logger_| is not null, return that. Otherwise
1298   // return |null_translate_metrics_logger_|. This way the callee doesn't have
1299   // to check if the returned value is null.
1300   return active_translate_metrics_logger_
1301              ? active_translate_metrics_logger_.get()
1302              : null_translate_metrics_logger_.get();
1303 }
1304 
RegisterTranslateMetricsLogger(base::WeakPtr<TranslateMetricsLogger> translate_metrics_logger)1305 void TranslateManager::RegisterTranslateMetricsLogger(
1306     base::WeakPtr<TranslateMetricsLogger> translate_metrics_logger) {
1307   active_translate_metrics_logger_ = translate_metrics_logger;
1308 }
1309 
1310 }  // namespace translate
1311