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
34 namespace translate {
35
36 namespace {
37
38 // Callbacks for translate errors.
39 TranslateManager::TranslateErrorCallbackList* g_callback_list_ = NULL;
40
41 const 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.
46 const char kSourceLanguageQueryName[] = "sl";
47
48 // Used in kReportLanguageDetectionErrorURL to specify the page URL.
49 const char kUrlQueryName[] = "u";
50
51 // Notifies |g_callback_list_| of translate errors.
NotifyTranslateError(const TranslateErrorDetails & details)52 void NotifyTranslateError(const TranslateErrorDetails& details) {
53 if (!g_callback_list_)
54 return;
55
56 g_callback_list_->Notify(details);
57 }
58
59 } // namespace
60
~TranslateManager()61 TranslateManager::~TranslateManager() {}
62
63 // static
64 scoped_ptr<TranslateManager::TranslateErrorCallbackList::Subscription>
RegisterTranslateErrorCallback(const TranslateManager::TranslateErrorCallback & callback)65 TranslateManager::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
TranslateManager(TranslateClient * translate_client,const std::string & accept_languages_pref_name)72 TranslateManager::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
GetWeakPtr()83 base::WeakPtr<TranslateManager> TranslateManager::GetWeakPtr() {
84 return weak_method_factory_.GetWeakPtr();
85 }
86
InitiateTranslation(const std::string & page_lang)87 void 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
TranslatePage(const std::string & original_source_lang,const std::string & target_lang,bool triggered_from_menu)217 void 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
RevertTranslation()257 void TranslateManager::RevertTranslation() {
258 translate_driver_->RevertTranslation(page_seq_no_);
259 language_state_.SetCurrentLanguage(language_state_.original_language());
260 }
261
ReportLanguageDetectionError()262 void 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
DoTranslatePage(const std::string & translate_script,const std::string & source_lang,const std::string & target_lang)283 void 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
PageTranslated(const std::string & source_lang,const std::string & target_lang,TranslateErrors::Type error_type)291 void 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
OnTranslateScriptFetchComplete(const std::string & source_lang,const std::string & target_lang,bool success,const std::string & data)319 void 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
GetTargetLanguage(const std::vector<std::string> & accept_languages_list)351 std::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
GetAutoTargetLanguage(const std::string & original_language,TranslatePrefs * translate_prefs)373 std::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
GetLanguageState()389 LanguageState& TranslateManager::GetLanguageState() {
390 return language_state_;
391 }
392
393 } // namespace translate
394