Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / translate / chrome_translate_client.cc
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/path_service.h"
11 #include "base/prefs/pref_service.h"
12 #include "base/strings/string_split.h"
13 #include "chrome/browser/chrome_notification_types.h"
14 #include "chrome/browser/infobars/infobar_service.h"
15 #include "chrome/browser/profiles/profile.h"
16 #include "chrome/browser/translate/translate_accept_languages_factory.h"
17 #include "chrome/browser/translate/translate_service.h"
18 #include "chrome/browser/ui/browser.h"
19 #include "chrome/browser/ui/browser_finder.h"
20 #include "chrome/browser/ui/browser_tabstrip.h"
21 #include "chrome/browser/ui/browser_window.h"
22 #include "chrome/browser/ui/tabs/tab_strip_model.h"
23 #include "chrome/browser/ui/translate/translate_bubble_factory.h"
24 #include "chrome/common/chrome_paths.h"
25 #include "chrome/common/pref_names.h"
26 #include "components/infobars/core/infobar.h"
27 #include "components/translate/content/common/cld_data_source.h"
28 #include "components/translate/content/common/translate_messages.h"
29 #include "components/translate/core/browser/language_state.h"
30 #include "components/translate/core/browser/page_translated_details.h"
31 #include "components/translate/core/browser/translate_accept_languages.h"
32 #include "components/translate/core/browser/translate_download_manager.h"
33 #include "components/translate/core/browser/translate_infobar_delegate.h"
34 #include "components/translate/core/browser/translate_manager.h"
35 #include "components/translate/core/browser/translate_prefs.h"
36 #include "components/translate/core/common/language_detection_details.h"
37 #include "content/public/browser/navigation_details.h"
38 #include "content/public/browser/navigation_entry.h"
39 #include "content/public/browser/notification_service.h"
40 #include "content/public/browser/render_view_host.h"
41 #include "content/public/browser/web_contents.h"
42 #include "grit/theme_resources.h"
43 #include "net/http/http_status_code.h"
44 #include "url/gurl.h"
45
46 namespace {
47
48 // The maximum number of attempts we'll do to see if the page has finshed
49 // loading before giving up the translation
50 const int kMaxTranslateLoadCheckAttempts = 20;
51
52 // TODO(andrewhayden): Make the data file path into a gyp/gn define
53 // If you change this, also update standalone_cld_data_harness.cc
54 // accordingly!
55 const base::FilePath::CharType kCldDataFileName[] =
56     FILE_PATH_LITERAL("cld2_data.bin");
57
58 bool g_cld_file_path_initialized_ = false;
59
60 }  // namespace
61
62 DEFINE_WEB_CONTENTS_USER_DATA_KEY(ChromeTranslateClient);
63
64 ChromeTranslateClient::ChromeTranslateClient(content::WebContents* web_contents)
65     : content::WebContentsObserver(web_contents),
66       max_reload_check_attempts_(kMaxTranslateLoadCheckAttempts),
67       translate_driver_(&web_contents->GetController()),
68       translate_manager_(
69           new translate::TranslateManager(this, prefs::kAcceptLanguages)),
70       cld_data_provider_(
71           translate::CreateBrowserCldDataProviderFor(web_contents)),
72       weak_pointer_factory_(this) {
73   // Customization: for the standalone data source, we configure the path to
74   // CLD data immediately on startup.
75   if (translate::CldDataSource::ShouldUseStandaloneDataFile() &&
76       !g_cld_file_path_initialized_) {
77     VLOG(1) << "Initializing CLD file path for the first time.";
78     base::FilePath path;
79     if (!PathService::Get(chrome::DIR_USER_DATA, &path)) {
80       // Chrome isn't properly installed
81       LOG(WARNING) << "Unable to locate user data directory";
82     } else {
83       g_cld_file_path_initialized_ = true;
84       path = path.Append(kCldDataFileName);
85       VLOG(1) << "Setting CLD data file path: " << path.value();
86       translate::SetCldDataFilePath(path);
87     }
88   }
89 }
90
91 ChromeTranslateClient::~ChromeTranslateClient() {
92 }
93
94 translate::LanguageState& ChromeTranslateClient::GetLanguageState() {
95   return translate_manager_->GetLanguageState();
96 }
97
98 // static
99 scoped_ptr<translate::TranslatePrefs>
100 ChromeTranslateClient::CreateTranslatePrefs(PrefService* prefs) {
101 #if defined(OS_CHROMEOS)
102   const char* preferred_languages_prefs = prefs::kLanguagePreferredLanguages;
103 #else
104   const char* preferred_languages_prefs = NULL;
105 #endif
106   return scoped_ptr<translate::TranslatePrefs>(new translate::TranslatePrefs(
107       prefs, prefs::kAcceptLanguages, preferred_languages_prefs));
108 }
109
110 // static
111 translate::TranslateAcceptLanguages*
112 ChromeTranslateClient::GetTranslateAcceptLanguages(
113     content::BrowserContext* browser_context) {
114   return TranslateAcceptLanguagesFactory::GetForBrowserContext(browser_context);
115 }
116
117 // static
118 translate::TranslateManager* ChromeTranslateClient::GetManagerFromWebContents(
119     content::WebContents* web_contents) {
120   ChromeTranslateClient* chrome_translate_client =
121       FromWebContents(web_contents);
122   if (!chrome_translate_client)
123     return NULL;
124   return chrome_translate_client->GetTranslateManager();
125 }
126
127 // static
128 void ChromeTranslateClient::GetTranslateLanguages(
129     content::WebContents* web_contents,
130     std::string* source,
131     std::string* target) {
132   DCHECK(source != NULL);
133   DCHECK(target != NULL);
134
135   ChromeTranslateClient* chrome_translate_client =
136       FromWebContents(web_contents);
137   if (!chrome_translate_client)
138     return;
139
140   *source = translate::TranslateDownloadManager::GetLanguageCode(
141       chrome_translate_client->GetLanguageState().original_language());
142
143   Profile* profile =
144       Profile::FromBrowserContext(web_contents->GetBrowserContext());
145   Profile* original_profile = profile->GetOriginalProfile();
146   PrefService* prefs = original_profile->GetPrefs();
147   scoped_ptr<translate::TranslatePrefs> translate_prefs =
148       CreateTranslatePrefs(prefs);
149   if (!web_contents->GetBrowserContext()->IsOffTheRecord()) {
150     std::string auto_translate_language =
151         translate::TranslateManager::GetAutoTargetLanguage(
152             *source, translate_prefs.get());
153     if (!auto_translate_language.empty()) {
154       *target = auto_translate_language;
155       return;
156     }
157   }
158
159   std::string accept_languages_str = prefs->GetString(prefs::kAcceptLanguages);
160   std::vector<std::string> accept_languages_list;
161   base::SplitString(accept_languages_str, ',', &accept_languages_list);
162   *target =
163       translate::TranslateManager::GetTargetLanguage(accept_languages_list);
164 }
165
166 translate::TranslateManager* ChromeTranslateClient::GetTranslateManager() {
167   return translate_manager_.get();
168 }
169
170 content::WebContents* ChromeTranslateClient::GetWebContents() {
171   return web_contents();
172 }
173
174 void ChromeTranslateClient::ShowTranslateUI(
175     translate::TranslateStep step,
176     const std::string source_language,
177     const std::string target_language,
178     translate::TranslateErrors::Type error_type,
179     bool triggered_from_menu) {
180   DCHECK(web_contents());
181   if (error_type != translate::TranslateErrors::NONE)
182     step = translate::TRANSLATE_STEP_TRANSLATE_ERROR;
183
184   if (TranslateService::IsTranslateBubbleEnabled()) {
185     // Bubble UI.
186     if (step == translate::TRANSLATE_STEP_BEFORE_TRANSLATE) {
187       // TODO(droger): Move this logic out of UI code.
188       GetLanguageState().SetTranslateEnabled(true);
189       if (!GetLanguageState().HasLanguageChanged())
190         return;
191
192       if (!triggered_from_menu) {
193         if (web_contents()->GetBrowserContext()->IsOffTheRecord())
194           return;
195         if (GetTranslatePrefs()->IsTooOftenDenied())
196           return;
197       }
198     }
199     ShowBubble(step, error_type);
200     return;
201   }
202
203   // Infobar UI.
204   translate::TranslateInfoBarDelegate::Create(
205       step != translate::TRANSLATE_STEP_BEFORE_TRANSLATE,
206       translate_manager_->GetWeakPtr(),
207       InfoBarService::FromWebContents(web_contents()),
208       web_contents()->GetBrowserContext()->IsOffTheRecord(),
209       step,
210       source_language,
211       target_language,
212       error_type,
213       triggered_from_menu);
214 }
215
216 translate::TranslateDriver* ChromeTranslateClient::GetTranslateDriver() {
217   return &translate_driver_;
218 }
219
220 PrefService* ChromeTranslateClient::GetPrefs() {
221   DCHECK(web_contents());
222   Profile* profile =
223       Profile::FromBrowserContext(web_contents()->GetBrowserContext());
224   return profile->GetOriginalProfile()->GetPrefs();
225 }
226
227 scoped_ptr<translate::TranslatePrefs>
228 ChromeTranslateClient::GetTranslatePrefs() {
229   DCHECK(web_contents());
230   Profile* profile =
231       Profile::FromBrowserContext(web_contents()->GetBrowserContext());
232   return CreateTranslatePrefs(profile->GetPrefs());
233 }
234
235 translate::TranslateAcceptLanguages*
236 ChromeTranslateClient::GetTranslateAcceptLanguages() {
237   DCHECK(web_contents());
238   return GetTranslateAcceptLanguages(web_contents()->GetBrowserContext());
239 }
240
241 int ChromeTranslateClient::GetInfobarIconID() const {
242   return IDR_INFOBAR_TRANSLATE;
243 }
244
245 // ChromeTranslateClient::CreateInfoBar() is implemented in platform-specific
246 // files, except the TOOLKIT_VIEWS implementation, which has been removed. Note
247 // for Mac, Cocoa is still providing the infobar in a toolkit-views build.
248 #if defined(TOOLKIT_VIEWS) && !defined(OS_MACOSX)
249 scoped_ptr<infobars::InfoBar> ChromeTranslateClient::CreateInfoBar(
250     scoped_ptr<translate::TranslateInfoBarDelegate> delegate) const {
251   return scoped_ptr<infobars::InfoBar>();
252 }
253 #endif
254
255 bool ChromeTranslateClient::IsTranslatableURL(const GURL& url) {
256   return TranslateService::IsTranslatableURL(url);
257 }
258
259 void ChromeTranslateClient::ShowReportLanguageDetectionErrorUI(
260     const GURL& report_url) {
261 #if defined(OS_ANDROID)
262   // Android does not support reporting language detection errors.
263   NOTREACHED();
264 #else
265   // We'll open the URL in a new tab so that the user can tell us more.
266   Browser* browser = chrome::FindBrowserWithWebContents(web_contents());
267   if (!browser) {
268     NOTREACHED();
269     return;
270   }
271
272   chrome::AddSelectedTabWithURL(
273       browser, report_url, content::PAGE_TRANSITION_AUTO_BOOKMARK);
274 #endif  // defined(OS_ANDROID)
275 }
276
277 bool ChromeTranslateClient::OnMessageReceived(const IPC::Message& message) {
278   bool handled = true;
279   IPC_BEGIN_MESSAGE_MAP(ChromeTranslateClient, message)
280   IPC_MESSAGE_HANDLER(ChromeViewHostMsg_TranslateAssignedSequenceNumber,
281                       OnTranslateAssignedSequenceNumber)
282   IPC_MESSAGE_HANDLER(ChromeViewHostMsg_TranslateLanguageDetermined,
283                       OnLanguageDetermined)
284   IPC_MESSAGE_HANDLER(ChromeViewHostMsg_PageTranslated, OnPageTranslated)
285   IPC_MESSAGE_UNHANDLED(handled = false)
286   IPC_END_MESSAGE_MAP()
287
288   if (!handled) {
289     handled = cld_data_provider_->OnMessageReceived(message);
290   }
291   return handled;
292 }
293
294 void ChromeTranslateClient::NavigationEntryCommitted(
295     const content::LoadCommittedDetails& load_details) {
296   // Check whether this is a reload: When doing a page reload, the
297   // TranslateLanguageDetermined IPC is not sent so the translation needs to be
298   // explicitly initiated.
299
300   content::NavigationEntry* entry =
301       web_contents()->GetController().GetActiveEntry();
302   if (!entry) {
303     NOTREACHED();
304     return;
305   }
306
307   // If the navigation happened while offline don't show the translate
308   // bar since there will be nothing to translate.
309   if (load_details.http_status_code == 0 ||
310       load_details.http_status_code == net::HTTP_INTERNAL_SERVER_ERROR) {
311     return;
312   }
313
314   if (!load_details.is_main_frame &&
315       GetLanguageState().translation_declined()) {
316     // Some sites (such as Google map) may trigger sub-frame navigations
317     // when the user interacts with the page.  We don't want to show a new
318     // infobar if the user already dismissed one in that case.
319     return;
320   }
321
322   // If not a reload, return.
323   if (entry->GetTransitionType() != content::PAGE_TRANSITION_RELOAD &&
324       load_details.type != content::NAVIGATION_TYPE_SAME_PAGE) {
325     return;
326   }
327
328   if (!GetLanguageState().page_needs_translation())
329     return;
330
331   // Note that we delay it as the ordering of the processing of this callback
332   // by WebContentsObservers is undefined and might result in the current
333   // infobars being removed. Since the translation initiation process might add
334   // an infobar, it must be done after that.
335   base::MessageLoop::current()->PostTask(
336       FROM_HERE,
337       base::Bind(&ChromeTranslateClient::InitiateTranslation,
338                  weak_pointer_factory_.GetWeakPtr(),
339                  GetLanguageState().original_language(),
340                  0));
341 }
342
343 void ChromeTranslateClient::DidNavigateAnyFrame(
344     const content::LoadCommittedDetails& details,
345     const content::FrameNavigateParams& params) {
346   // Let the LanguageState clear its state.
347   const bool reload =
348       details.entry->GetTransitionType() == content::PAGE_TRANSITION_RELOAD ||
349       details.type == content::NAVIGATION_TYPE_SAME_PAGE;
350   GetLanguageState().DidNavigate(
351       details.is_in_page, details.is_main_frame, reload);
352 }
353
354 void ChromeTranslateClient::WebContentsDestroyed() {
355   // Translation process can be interrupted.
356   // Destroying the TranslateManager now guarantees that it never has to deal
357   // with NULL WebContents.
358   translate_manager_.reset();
359 }
360
361 void ChromeTranslateClient::InitiateTranslation(const std::string& page_lang,
362                                                 int attempt) {
363   if (GetLanguageState().translation_pending())
364     return;
365
366   // During a reload we need web content to be available before the
367   // translate script is executed. Otherwise we will run the translate script on
368   // an empty DOM which will fail. Therefore we wait a bit to see if the page
369   // has finished.
370   if (web_contents()->IsLoading() && attempt < max_reload_check_attempts_) {
371     int backoff = attempt * kMaxTranslateLoadCheckAttempts;
372     base::MessageLoop::current()->PostDelayedTask(
373         FROM_HERE,
374         base::Bind(&ChromeTranslateClient::InitiateTranslation,
375                    weak_pointer_factory_.GetWeakPtr(),
376                    page_lang,
377                    attempt + 1),
378         base::TimeDelta::FromMilliseconds(backoff));
379     return;
380   }
381
382   translate_manager_->InitiateTranslation(
383       translate::TranslateDownloadManager::GetLanguageCode(page_lang));
384 }
385
386 void ChromeTranslateClient::OnTranslateAssignedSequenceNumber(int page_seq_no) {
387   translate_manager_->set_current_seq_no(page_seq_no);
388 }
389
390 void ChromeTranslateClient::OnLanguageDetermined(
391     const translate::LanguageDetectionDetails& details,
392     bool page_needs_translation) {
393   GetLanguageState().LanguageDetermined(details.adopted_language,
394                                         page_needs_translation);
395
396   if (web_contents())
397     translate_manager_->InitiateTranslation(details.adopted_language);
398
399   content::NotificationService::current()->Notify(
400       chrome::NOTIFICATION_TAB_LANGUAGE_DETERMINED,
401       content::Source<content::WebContents>(web_contents()),
402       content::Details<const translate::LanguageDetectionDetails>(&details));
403 }
404
405 void ChromeTranslateClient::OnPageTranslated(
406     const std::string& original_lang,
407     const std::string& translated_lang,
408     translate::TranslateErrors::Type error_type) {
409   DCHECK(web_contents());
410   translate_manager_->PageTranslated(
411       original_lang, translated_lang, error_type);
412
413   translate::PageTranslatedDetails details;
414   details.source_language = original_lang;
415   details.target_language = translated_lang;
416   details.error_type = error_type;
417   content::NotificationService::current()->Notify(
418       chrome::NOTIFICATION_PAGE_TRANSLATED,
419       content::Source<content::WebContents>(web_contents()),
420       content::Details<translate::PageTranslatedDetails>(&details));
421 }
422
423 void ChromeTranslateClient::ShowBubble(
424     translate::TranslateStep step,
425     translate::TranslateErrors::Type error_type) {
426 // The bubble is implemented only on the desktop platforms.
427 #if !defined(OS_ANDROID) && !defined(OS_IOS)
428   Browser* browser = chrome::FindBrowserWithWebContents(web_contents());
429
430   // |browser| might be NULL when testing. In this case, Show(...) should be
431   // called because the implementation for testing is used.
432   if (!browser) {
433     TranslateBubbleFactory::Show(NULL, web_contents(), step, error_type);
434     return;
435   }
436
437   if (web_contents() != browser->tab_strip_model()->GetActiveWebContents())
438     return;
439
440   // This ShowBubble function is also used for upating the existing bubble.
441   // However, with the bubble shown, any browser windows are NOT activated
442   // because the bubble takes the focus from the other widgets including the
443   // browser windows. So it is checked that |browser| is the last activated
444   // browser, not is now activated.
445   if (browser !=
446       chrome::FindLastActiveWithHostDesktopType(browser->host_desktop_type())) {
447     return;
448   }
449
450   // During auto-translating, the bubble should not be shown.
451   if (step == translate::TRANSLATE_STEP_TRANSLATING ||
452       step == translate::TRANSLATE_STEP_AFTER_TRANSLATE) {
453     if (GetLanguageState().InTranslateNavigation())
454       return;
455   }
456
457   TranslateBubbleFactory::Show(
458       browser->window(), web_contents(), step, error_type);
459 #else
460   NOTREACHED();
461 #endif
462 }