chrome_translate_client.cc revision 5f1c94371a64b3196d4be9466099bb892df9b88e
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
46namespace {
47
48// The maximum number of attempts we'll do to see if the page has finshed
49// loading before giving up the translation
50const 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!
55const base::FilePath::CharType kCldDataFileName[] =
56    FILE_PATH_LITERAL("cld2_data.bin");
57
58bool g_cld_file_path_initialized_ = false;
59
60}  // namespace
61
62DEFINE_WEB_CONTENTS_USER_DATA_KEY(ChromeTranslateClient);
63
64ChromeTranslateClient::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
91ChromeTranslateClient::~ChromeTranslateClient() {
92}
93
94translate::LanguageState& ChromeTranslateClient::GetLanguageState() {
95  return translate_manager_->GetLanguageState();
96}
97
98// static
99scoped_ptr<translate::TranslatePrefs>
100ChromeTranslateClient::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
111translate::TranslateAcceptLanguages*
112ChromeTranslateClient::GetTranslateAcceptLanguages(
113    content::BrowserContext* browser_context) {
114  return TranslateAcceptLanguagesFactory::GetForBrowserContext(browser_context);
115}
116
117// static
118translate::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
128void 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
166translate::TranslateManager* ChromeTranslateClient::GetTranslateManager() {
167  return translate_manager_.get();
168}
169
170content::WebContents* ChromeTranslateClient::GetWebContents() {
171  return web_contents();
172}
173
174void 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
216translate::TranslateDriver* ChromeTranslateClient::GetTranslateDriver() {
217  return &translate_driver_;
218}
219
220PrefService* ChromeTranslateClient::GetPrefs() {
221  DCHECK(web_contents());
222  Profile* profile =
223      Profile::FromBrowserContext(web_contents()->GetBrowserContext());
224  return profile->GetOriginalProfile()->GetPrefs();
225}
226
227scoped_ptr<translate::TranslatePrefs>
228ChromeTranslateClient::GetTranslatePrefs() {
229  DCHECK(web_contents());
230  Profile* profile =
231      Profile::FromBrowserContext(web_contents()->GetBrowserContext());
232  return CreateTranslatePrefs(profile->GetPrefs());
233}
234
235translate::TranslateAcceptLanguages*
236ChromeTranslateClient::GetTranslateAcceptLanguages() {
237  DCHECK(web_contents());
238  return GetTranslateAcceptLanguages(web_contents()->GetBrowserContext());
239}
240
241int 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)
249scoped_ptr<infobars::InfoBar> ChromeTranslateClient::CreateInfoBar(
250    scoped_ptr<translate::TranslateInfoBarDelegate> delegate) const {
251  return scoped_ptr<infobars::InfoBar>();
252}
253#endif
254
255bool ChromeTranslateClient::IsTranslatableURL(const GURL& url) {
256  return TranslateService::IsTranslatableURL(url);
257}
258
259void 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
277bool 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
294void 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
343void 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
354void 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
361void 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
386void ChromeTranslateClient::OnTranslateAssignedSequenceNumber(int page_seq_no) {
387  translate_manager_->set_current_seq_no(page_seq_no);
388}
389
390void 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
405void 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
423void 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}
463