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 "chrome/browser/translate/chrome_translate_client.h"
6
7 #include <vector>
8
9 #include "base/logging.h"
10 #include "base/prefs/pref_service.h"
11 #include "base/strings/string_split.h"
12 #include "chrome/browser/chrome_notification_types.h"
13 #include "chrome/browser/infobars/infobar_service.h"
14 #include "chrome/browser/profiles/profile.h"
15 #include "chrome/browser/translate/translate_accept_languages_factory.h"
16 #include "chrome/browser/translate/translate_service.h"
17 #include "chrome/browser/ui/browser.h"
18 #include "chrome/browser/ui/browser_finder.h"
19 #include "chrome/browser/ui/browser_tabstrip.h"
20 #include "chrome/browser/ui/browser_window.h"
21 #include "chrome/browser/ui/tabs/tab_strip_model.h"
22 #include "chrome/browser/ui/translate/translate_bubble_factory.h"
23 #include "chrome/common/pref_names.h"
24 #include "components/infobars/core/infobar.h"
25 #include "components/translate/content/common/translate_messages.h"
26 #include "components/translate/core/browser/language_state.h"
27 #include "components/translate/core/browser/page_translated_details.h"
28 #include "components/translate/core/browser/translate_accept_languages.h"
29 #include "components/translate/core/browser/translate_download_manager.h"
30 #include "components/translate/core/browser/translate_infobar_delegate.h"
31 #include "components/translate/core/browser/translate_manager.h"
32 #include "components/translate/core/browser/translate_prefs.h"
33 #include "components/translate/core/common/language_detection_details.h"
34 #include "content/public/browser/navigation_details.h"
35 #include "content/public/browser/navigation_entry.h"
36 #include "content/public/browser/notification_service.h"
37 #include "content/public/browser/render_view_host.h"
38 #include "content/public/browser/web_contents.h"
39 #include "grit/theme_resources.h"
40 #include "net/http/http_status_code.h"
41 #include "url/gurl.h"
42
43 #if defined(CLD2_DYNAMIC_MODE)
44 #include "base/files/file.h"
45 #include "base/path_service.h"
46 #include "chrome/common/chrome_constants.h"
47 #include "chrome/common/chrome_paths.h"
48 #include "content/public/browser/browser_thread.h"
49 #include "content/public/browser/render_process_host.h"
50 #endif
51
52 #if defined(CLD2_IS_COMPONENT)
53 #include "chrome/browser/component_updater/cld_component_installer.h"
54 #endif
55
56 namespace {
57
58 // The maximum number of attempts we'll do to see if the page has finshed
59 // loading before giving up the translation
60 const int kMaxTranslateLoadCheckAttempts = 20;
61
62 } // namespace
63
64 DEFINE_WEB_CONTENTS_USER_DATA_KEY(ChromeTranslateClient);
65
66 #if defined(CLD2_DYNAMIC_MODE)
67 // Statics defined in the .h file:
68 base::File* ChromeTranslateClient::s_cached_file_ = NULL;
69 uint64 ChromeTranslateClient::s_cached_data_offset_ = 0;
70 uint64 ChromeTranslateClient::s_cached_data_length_ = 0;
71 base::LazyInstance<base::Lock> ChromeTranslateClient::s_file_lock_ =
72 LAZY_INSTANCE_INITIALIZER;
73 #endif
74
ChromeTranslateClient(content::WebContents * web_contents)75 ChromeTranslateClient::ChromeTranslateClient(content::WebContents* web_contents)
76 : content::WebContentsObserver(web_contents),
77 max_reload_check_attempts_(kMaxTranslateLoadCheckAttempts),
78 translate_driver_(&web_contents->GetController()),
79 translate_manager_(new TranslateManager(this, prefs::kAcceptLanguages)),
80 weak_pointer_factory_(this) {
81 }
82
~ChromeTranslateClient()83 ChromeTranslateClient::~ChromeTranslateClient() {
84 }
85
GetLanguageState()86 LanguageState& ChromeTranslateClient::GetLanguageState() {
87 return translate_manager_->GetLanguageState();
88 }
89
90 // static
CreateTranslatePrefs(PrefService * prefs)91 scoped_ptr<TranslatePrefs> ChromeTranslateClient::CreateTranslatePrefs(
92 PrefService* prefs) {
93 #if defined(OS_CHROMEOS)
94 const char* preferred_languages_prefs = prefs::kLanguagePreferredLanguages;
95 #else
96 const char* preferred_languages_prefs = NULL;
97 #endif
98 return scoped_ptr<TranslatePrefs>(new TranslatePrefs(
99 prefs, prefs::kAcceptLanguages, preferred_languages_prefs));
100 }
101
102 // static
GetTranslateAcceptLanguages(content::BrowserContext * browser_context)103 TranslateAcceptLanguages* ChromeTranslateClient::GetTranslateAcceptLanguages(
104 content::BrowserContext* browser_context) {
105 return TranslateAcceptLanguagesFactory::GetForBrowserContext(browser_context);
106 }
107
108 // static
GetManagerFromWebContents(content::WebContents * web_contents)109 TranslateManager* ChromeTranslateClient::GetManagerFromWebContents(
110 content::WebContents* web_contents) {
111 ChromeTranslateClient* chrome_translate_client =
112 FromWebContents(web_contents);
113 if (!chrome_translate_client)
114 return NULL;
115 return chrome_translate_client->GetTranslateManager();
116 }
117
118 // static
GetTranslateLanguages(content::WebContents * web_contents,std::string * source,std::string * target)119 void ChromeTranslateClient::GetTranslateLanguages(
120 content::WebContents* web_contents,
121 std::string* source,
122 std::string* target) {
123 DCHECK(source != NULL);
124 DCHECK(target != NULL);
125
126 ChromeTranslateClient* chrome_translate_client =
127 FromWebContents(web_contents);
128 if (!chrome_translate_client)
129 return;
130
131 *source = TranslateDownloadManager::GetLanguageCode(
132 chrome_translate_client->GetLanguageState().original_language());
133
134 Profile* profile =
135 Profile::FromBrowserContext(web_contents->GetBrowserContext());
136 Profile* original_profile = profile->GetOriginalProfile();
137 PrefService* prefs = original_profile->GetPrefs();
138 scoped_ptr<TranslatePrefs> translate_prefs = CreateTranslatePrefs(prefs);
139 if (!web_contents->GetBrowserContext()->IsOffTheRecord()) {
140 std::string auto_translate_language =
141 TranslateManager::GetAutoTargetLanguage(*source, translate_prefs.get());
142 if (!auto_translate_language.empty()) {
143 *target = auto_translate_language;
144 return;
145 }
146 }
147
148 std::string accept_languages_str = prefs->GetString(prefs::kAcceptLanguages);
149 std::vector<std::string> accept_languages_list;
150 base::SplitString(accept_languages_str, ',', &accept_languages_list);
151 *target = TranslateManager::GetTargetLanguage(accept_languages_list);
152 }
153
GetTranslateManager()154 TranslateManager* ChromeTranslateClient::GetTranslateManager() {
155 return translate_manager_.get();
156 }
157
GetWebContents()158 content::WebContents* ChromeTranslateClient::GetWebContents() {
159 return web_contents();
160 }
161
ShowTranslateUI(translate::TranslateStep step,const std::string source_language,const std::string target_language,TranslateErrors::Type error_type,bool triggered_from_menu)162 void ChromeTranslateClient::ShowTranslateUI(translate::TranslateStep step,
163 const std::string source_language,
164 const std::string target_language,
165 TranslateErrors::Type error_type,
166 bool triggered_from_menu) {
167 DCHECK(web_contents());
168 if (error_type != TranslateErrors::NONE)
169 step = translate::TRANSLATE_STEP_TRANSLATE_ERROR;
170
171 if (TranslateService::IsTranslateBubbleEnabled()) {
172 // Bubble UI.
173 if (step == translate::TRANSLATE_STEP_BEFORE_TRANSLATE) {
174 // TODO(droger): Move this logic out of UI code.
175 GetLanguageState().SetTranslateEnabled(true);
176 if (!GetLanguageState().HasLanguageChanged())
177 return;
178 }
179 ShowBubble(step, error_type);
180 return;
181 }
182
183 // Infobar UI.
184 TranslateInfoBarDelegate::Create(
185 step != translate::TRANSLATE_STEP_BEFORE_TRANSLATE,
186 translate_manager_->GetWeakPtr(),
187 InfoBarService::FromWebContents(web_contents()),
188 web_contents()->GetBrowserContext()->IsOffTheRecord(),
189 step,
190 source_language,
191 target_language,
192 error_type,
193 triggered_from_menu);
194 }
195
GetTranslateDriver()196 TranslateDriver* ChromeTranslateClient::GetTranslateDriver() {
197 return &translate_driver_;
198 }
199
GetPrefs()200 PrefService* ChromeTranslateClient::GetPrefs() {
201 DCHECK(web_contents());
202 Profile* profile =
203 Profile::FromBrowserContext(web_contents()->GetBrowserContext());
204 return profile->GetOriginalProfile()->GetPrefs();
205 }
206
GetTranslatePrefs()207 scoped_ptr<TranslatePrefs> ChromeTranslateClient::GetTranslatePrefs() {
208 DCHECK(web_contents());
209 Profile* profile =
210 Profile::FromBrowserContext(web_contents()->GetBrowserContext());
211 return CreateTranslatePrefs(profile->GetPrefs());
212 }
213
GetTranslateAcceptLanguages()214 TranslateAcceptLanguages* ChromeTranslateClient::GetTranslateAcceptLanguages() {
215 DCHECK(web_contents());
216 return GetTranslateAcceptLanguages(web_contents()->GetBrowserContext());
217 }
218
GetInfobarIconID() const219 int ChromeTranslateClient::GetInfobarIconID() const {
220 return IDR_INFOBAR_TRANSLATE;
221 }
222
223 // ChromeTranslateClient::CreateInfoBar() is implemented in platform-specific
224 // files, except the TOOLKIT_VIEWS implementation, which has been removed.
225 #if defined(TOOLKIT_VIEWS)
CreateInfoBar(scoped_ptr<TranslateInfoBarDelegate> delegate) const226 scoped_ptr<infobars::InfoBar> ChromeTranslateClient::CreateInfoBar(
227 scoped_ptr<TranslateInfoBarDelegate> delegate) const {
228 return scoped_ptr<infobars::InfoBar>();
229 }
230 #endif
231
IsTranslatableURL(const GURL & url)232 bool ChromeTranslateClient::IsTranslatableURL(const GURL& url) {
233 return TranslateService::IsTranslatableURL(url);
234 }
235
ShowReportLanguageDetectionErrorUI(const GURL & report_url)236 void ChromeTranslateClient::ShowReportLanguageDetectionErrorUI(
237 const GURL& report_url) {
238 #if defined(OS_ANDROID)
239 // Android does not support reporting language detection errors.
240 NOTREACHED();
241 #else
242 // We'll open the URL in a new tab so that the user can tell us more.
243 Browser* browser = chrome::FindBrowserWithWebContents(web_contents());
244 if (!browser) {
245 NOTREACHED();
246 return;
247 }
248
249 chrome::AddSelectedTabWithURL(
250 browser, report_url, content::PAGE_TRANSITION_AUTO_BOOKMARK);
251 #endif // defined(OS_ANDROID)
252 }
253
OnMessageReceived(const IPC::Message & message)254 bool ChromeTranslateClient::OnMessageReceived(const IPC::Message& message) {
255 bool handled = true;
256 IPC_BEGIN_MESSAGE_MAP(ChromeTranslateClient, message)
257 IPC_MESSAGE_HANDLER(ChromeViewHostMsg_TranslateLanguageDetermined,
258 OnLanguageDetermined)
259 IPC_MESSAGE_HANDLER(ChromeViewHostMsg_PageTranslated, OnPageTranslated)
260 #if defined(CLD2_DYNAMIC_MODE)
261 IPC_MESSAGE_HANDLER(ChromeViewHostMsg_NeedCLDData, OnCLDDataRequested)
262 #endif
263 IPC_MESSAGE_UNHANDLED(handled = false)
264 IPC_END_MESSAGE_MAP()
265
266 return handled;
267 }
268
NavigationEntryCommitted(const content::LoadCommittedDetails & load_details)269 void ChromeTranslateClient::NavigationEntryCommitted(
270 const content::LoadCommittedDetails& load_details) {
271 // Check whether this is a reload: When doing a page reload, the
272 // TranslateLanguageDetermined IPC is not sent so the translation needs to be
273 // explicitly initiated.
274
275 content::NavigationEntry* entry =
276 web_contents()->GetController().GetActiveEntry();
277 if (!entry) {
278 NOTREACHED();
279 return;
280 }
281
282 // If the navigation happened while offline don't show the translate
283 // bar since there will be nothing to translate.
284 if (load_details.http_status_code == 0 ||
285 load_details.http_status_code == net::HTTP_INTERNAL_SERVER_ERROR) {
286 return;
287 }
288
289 if (!load_details.is_main_frame &&
290 GetLanguageState().translation_declined()) {
291 // Some sites (such as Google map) may trigger sub-frame navigations
292 // when the user interacts with the page. We don't want to show a new
293 // infobar if the user already dismissed one in that case.
294 return;
295 }
296
297 // If not a reload, return.
298 if (entry->GetTransitionType() != content::PAGE_TRANSITION_RELOAD &&
299 load_details.type != content::NAVIGATION_TYPE_SAME_PAGE) {
300 return;
301 }
302
303 if (!GetLanguageState().page_needs_translation())
304 return;
305
306 // Note that we delay it as the ordering of the processing of this callback
307 // by WebContentsObservers is undefined and might result in the current
308 // infobars being removed. Since the translation initiation process might add
309 // an infobar, it must be done after that.
310 base::MessageLoop::current()->PostTask(
311 FROM_HERE,
312 base::Bind(&ChromeTranslateClient::InitiateTranslation,
313 weak_pointer_factory_.GetWeakPtr(),
314 GetLanguageState().original_language(),
315 0));
316 }
317
DidNavigateAnyFrame(const content::LoadCommittedDetails & details,const content::FrameNavigateParams & params)318 void ChromeTranslateClient::DidNavigateAnyFrame(
319 const content::LoadCommittedDetails& details,
320 const content::FrameNavigateParams& params) {
321 // Let the LanguageState clear its state.
322 const bool reload =
323 details.entry->GetTransitionType() == content::PAGE_TRANSITION_RELOAD ||
324 details.type == content::NAVIGATION_TYPE_SAME_PAGE;
325 GetLanguageState().DidNavigate(
326 details.is_in_page, details.is_main_frame, reload);
327 }
328
WebContentsDestroyed()329 void ChromeTranslateClient::WebContentsDestroyed() {
330 // Translation process can be interrupted.
331 // Destroying the TranslateManager now guarantees that it never has to deal
332 // with NULL WebContents.
333 translate_manager_.reset();
334 }
335
336 #if defined(CLD2_DYNAMIC_MODE)
OnCLDDataRequested()337 void ChromeTranslateClient::OnCLDDataRequested() {
338 // Quickly try to read s_cached_file_. If valid, the file handle is
339 // cached and can be used immediately. Else, queue the caching task to the
340 // blocking pool.
341 base::File* handle = NULL;
342 uint64 data_offset = 0;
343 uint64 data_length = 0;
344 {
345 base::AutoLock lock(s_file_lock_.Get());
346 handle = s_cached_file_;
347 data_offset = s_cached_data_offset_;
348 data_length = s_cached_data_length_;
349 }
350
351 if (handle && handle->IsValid()) {
352 // Cached data available. Respond to the request.
353 SendCLDDataAvailable(handle, data_offset, data_length);
354 return;
355 }
356
357 // Else, we don't have the data file yet. Queue a caching attempt.
358 // The caching attempt happens in the blocking pool because it may involve
359 // arbitrary filesystem access.
360 // After the caching attempt is made, we call MaybeSendCLDDataAvailable
361 // to pass the file handle to the renderer. This only results in an IPC
362 // message if the caching attempt was successful.
363 content::BrowserThread::PostBlockingPoolTaskAndReply(
364 FROM_HERE,
365 base::Bind(&ChromeTranslateClient::HandleCLDDataRequest),
366 base::Bind(&ChromeTranslateClient::MaybeSendCLDDataAvailable,
367 weak_pointer_factory_.GetWeakPtr()));
368 }
369
MaybeSendCLDDataAvailable()370 void ChromeTranslateClient::MaybeSendCLDDataAvailable() {
371 base::File* handle = NULL;
372 uint64 data_offset = 0;
373 uint64 data_length = 0;
374 {
375 base::AutoLock lock(s_file_lock_.Get());
376 handle = s_cached_file_;
377 data_offset = s_cached_data_offset_;
378 data_length = s_cached_data_length_;
379 }
380
381 if (handle && handle->IsValid())
382 SendCLDDataAvailable(handle, data_offset, data_length);
383 }
384
SendCLDDataAvailable(const base::File * handle,const uint64 data_offset,const uint64 data_length)385 void ChromeTranslateClient::SendCLDDataAvailable(const base::File* handle,
386 const uint64 data_offset,
387 const uint64 data_length) {
388 // Data available, respond to the request.
389 IPC::PlatformFileForTransit ipc_platform_file = IPC::GetFileHandleForProcess(
390 handle->GetPlatformFile(),
391 GetWebContents()->GetRenderViewHost()->GetProcess()->GetHandle(),
392 false);
393 // In general, sending a response from within the code path that is processing
394 // a request is discouraged because there is potential for deadlock (if the
395 // methods are sent synchronously) or loops (if the response can trigger a
396 // new request). Neither of these concerns is relevant in this code, so
397 // sending the response from within the code path of the request handler is
398 // safe.
399 Send(new ChromeViewMsg_CLDDataAvailable(
400 GetWebContents()->GetRenderViewHost()->GetRoutingID(),
401 ipc_platform_file,
402 data_offset,
403 data_length));
404 }
405
HandleCLDDataRequest()406 void ChromeTranslateClient::HandleCLDDataRequest() {
407 // Because this function involves arbitrary file system access, it must run
408 // on the blocking pool.
409 DCHECK(!content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
410 DCHECK(!content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
411
412 {
413 base::AutoLock lock(s_file_lock_.Get());
414 if (s_cached_file_)
415 return; // Already done, duplicate request
416 }
417
418 #if defined(CLD2_IS_COMPONENT)
419 base::FilePath path = component_updater::GetLatestCldDataFile();
420 if (path.empty())
421 return;
422 #else // CLD2 data is at a well-known file path
423 base::FilePath path;
424 if (!PathService::Get(chrome::DIR_USER_DATA, &path)) {
425 LOG(WARNING) << "Unable to locate user data directory";
426 return; // Chrome isn't properly installed.
427 }
428 path = path.Append(chrome::kCLDDataFilename);
429 #endif
430
431 // If the file exists, we can send an IPC-safe construct back to the
432 // renderer process immediately; otherwise, nothing to do here.
433 if (!base::PathExists(path))
434 return;
435
436 // Attempt to open the file for reading.
437 scoped_ptr<base::File> file(
438 new base::File(path, base::File::FLAG_OPEN | base::File::FLAG_READ));
439 if (!file->IsValid()) {
440 LOG(WARNING) << "CLD data file exists but cannot be opened";
441 return;
442 }
443
444 base::File::Info file_info;
445 if (!file->GetInfo(&file_info)) {
446 LOG(WARNING) << "CLD data file exists but cannot be inspected";
447 return;
448 }
449
450 // For now, our offset and length are simply 0 and the length of the file,
451 // respectively. If we later decide to include the CLD2 data file inside of
452 // a larger binary context, these params can be twiddled appropriately.
453 const uint64 data_offset = 0;
454 const uint64 data_length = file_info.size;
455
456 {
457 base::AutoLock lock(s_file_lock_.Get());
458 if (s_cached_file_) {
459 // Idempotence: Racing another request on the blocking pool, abort.
460 } else {
461 // Else, this request has taken care of it all. Cache all info.
462 s_cached_file_ = file.release();
463 s_cached_data_offset_ = data_offset;
464 s_cached_data_length_ = data_length;
465 }
466 }
467 }
468
469 #endif // defined(CLD2_DYNAMIC_MODE)
470
InitiateTranslation(const std::string & page_lang,int attempt)471 void ChromeTranslateClient::InitiateTranslation(const std::string& page_lang,
472 int attempt) {
473 if (GetLanguageState().translation_pending())
474 return;
475
476 // During a reload we need web content to be available before the
477 // translate script is executed. Otherwise we will run the translate script on
478 // an empty DOM which will fail. Therefore we wait a bit to see if the page
479 // has finished.
480 if (web_contents()->IsLoading() && attempt < max_reload_check_attempts_) {
481 int backoff = attempt * kMaxTranslateLoadCheckAttempts;
482 base::MessageLoop::current()->PostDelayedTask(
483 FROM_HERE,
484 base::Bind(&ChromeTranslateClient::InitiateTranslation,
485 weak_pointer_factory_.GetWeakPtr(),
486 page_lang,
487 ++attempt),
488 base::TimeDelta::FromMilliseconds(backoff));
489 return;
490 }
491
492 translate_manager_->InitiateTranslation(
493 TranslateDownloadManager::GetLanguageCode(page_lang));
494 }
495
OnLanguageDetermined(const LanguageDetectionDetails & details,bool page_needs_translation)496 void ChromeTranslateClient::OnLanguageDetermined(
497 const LanguageDetectionDetails& details,
498 bool page_needs_translation) {
499 GetLanguageState().LanguageDetermined(details.adopted_language,
500 page_needs_translation);
501
502 if (web_contents())
503 translate_manager_->InitiateTranslation(details.adopted_language);
504
505 content::NotificationService::current()->Notify(
506 chrome::NOTIFICATION_TAB_LANGUAGE_DETERMINED,
507 content::Source<content::WebContents>(web_contents()),
508 content::Details<const LanguageDetectionDetails>(&details));
509 }
510
OnPageTranslated(int32 page_id,const std::string & original_lang,const std::string & translated_lang,TranslateErrors::Type error_type)511 void ChromeTranslateClient::OnPageTranslated(int32 page_id,
512 const std::string& original_lang,
513 const std::string& translated_lang,
514 TranslateErrors::Type error_type) {
515 DCHECK(web_contents());
516 translate_manager_->PageTranslated(
517 original_lang, translated_lang, error_type);
518
519 PageTranslatedDetails details;
520 details.source_language = original_lang;
521 details.target_language = translated_lang;
522 details.error_type = error_type;
523 content::NotificationService::current()->Notify(
524 chrome::NOTIFICATION_PAGE_TRANSLATED,
525 content::Source<content::WebContents>(web_contents()),
526 content::Details<PageTranslatedDetails>(&details));
527 }
528
ShowBubble(translate::TranslateStep step,TranslateErrors::Type error_type)529 void ChromeTranslateClient::ShowBubble(translate::TranslateStep step,
530 TranslateErrors::Type error_type) {
531 // The bubble is implemented only on the desktop platforms.
532 #if !defined(OS_ANDROID) && !defined(OS_IOS)
533 Browser* browser = chrome::FindBrowserWithWebContents(web_contents());
534
535 // |browser| might be NULL when testing. In this case, Show(...) should be
536 // called because the implementation for testing is used.
537 if (!browser) {
538 TranslateBubbleFactory::Show(NULL, web_contents(), step, error_type);
539 return;
540 }
541
542 if (web_contents() != browser->tab_strip_model()->GetActiveWebContents())
543 return;
544
545 // This ShowBubble function is also used for upating the existing bubble.
546 // However, with the bubble shown, any browser windows are NOT activated
547 // because the bubble takes the focus from the other widgets including the
548 // browser windows. So it is checked that |browser| is the last activated
549 // browser, not is now activated.
550 if (browser !=
551 chrome::FindLastActiveWithHostDesktopType(browser->host_desktop_type())) {
552 return;
553 }
554
555 // During auto-translating, the bubble should not be shown.
556 if (step == translate::TRANSLATE_STEP_TRANSLATING ||
557 step == translate::TRANSLATE_STEP_AFTER_TRANSLATE) {
558 if (GetLanguageState().InTranslateNavigation())
559 return;
560 }
561
562 TranslateBubbleFactory::Show(
563 browser->window(), web_contents(), step, error_type);
564 #else
565 NOTREACHED();
566 #endif
567 }
568