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