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/notification_service.h"
38#include "content/public/browser/render_view_host.h"
39#include "content/public/browser/web_contents.h"
40#include "grit/theme_resources.h"
41#include "url/gurl.h"
42
43namespace {
44
45// TODO(andrewhayden): Make the data file path into a gyp/gn define
46// If you change this, also update standalone_cld_data_harness.cc
47// accordingly!
48const base::FilePath::CharType kCldDataFileName[] =
49    FILE_PATH_LITERAL("cld2_data.bin");
50
51bool g_cld_file_path_initialized_ = false;
52
53}  // namespace
54
55DEFINE_WEB_CONTENTS_USER_DATA_KEY(ChromeTranslateClient);
56
57ChromeTranslateClient::ChromeTranslateClient(content::WebContents* web_contents)
58    : content::WebContentsObserver(web_contents),
59      translate_driver_(&web_contents->GetController()),
60      translate_manager_(
61          new translate::TranslateManager(this, prefs::kAcceptLanguages)),
62      cld_data_provider_(
63          translate::CreateBrowserCldDataProviderFor(web_contents)) {
64  translate_driver_.AddObserver(this);
65  translate_driver_.set_translate_manager(translate_manager_.get());
66  // Customization: for the standalone data source, we configure the path to
67  // CLD data immediately on startup.
68  if (translate::CldDataSource::ShouldUseStandaloneDataFile() &&
69      !g_cld_file_path_initialized_) {
70    VLOG(1) << "Initializing CLD file path for the first time.";
71    base::FilePath path;
72    if (!PathService::Get(chrome::DIR_USER_DATA, &path)) {
73      // Chrome isn't properly installed
74      LOG(WARNING) << "Unable to locate user data directory";
75    } else {
76      g_cld_file_path_initialized_ = true;
77      path = path.Append(kCldDataFileName);
78      VLOG(1) << "Setting CLD data file path: " << path.value();
79      translate::SetCldDataFilePath(path);
80    }
81  }
82}
83
84ChromeTranslateClient::~ChromeTranslateClient() {
85  translate_driver_.RemoveObserver(this);
86}
87
88translate::LanguageState& ChromeTranslateClient::GetLanguageState() {
89  return translate_manager_->GetLanguageState();
90}
91
92// static
93scoped_ptr<translate::TranslatePrefs>
94ChromeTranslateClient::CreateTranslatePrefs(PrefService* prefs) {
95#if defined(OS_CHROMEOS)
96  const char* preferred_languages_prefs = prefs::kLanguagePreferredLanguages;
97#else
98  const char* preferred_languages_prefs = NULL;
99#endif
100  return scoped_ptr<translate::TranslatePrefs>(new translate::TranslatePrefs(
101      prefs, prefs::kAcceptLanguages, preferred_languages_prefs));
102}
103
104// static
105translate::TranslateAcceptLanguages*
106ChromeTranslateClient::GetTranslateAcceptLanguages(
107    content::BrowserContext* browser_context) {
108  return TranslateAcceptLanguagesFactory::GetForBrowserContext(browser_context);
109}
110
111// static
112translate::TranslateManager* ChromeTranslateClient::GetManagerFromWebContents(
113    content::WebContents* web_contents) {
114  ChromeTranslateClient* chrome_translate_client =
115      FromWebContents(web_contents);
116  if (!chrome_translate_client)
117    return NULL;
118  return chrome_translate_client->GetTranslateManager();
119}
120
121// static
122void ChromeTranslateClient::GetTranslateLanguages(
123    content::WebContents* web_contents,
124    std::string* source,
125    std::string* target) {
126  DCHECK(source != NULL);
127  DCHECK(target != NULL);
128
129  ChromeTranslateClient* chrome_translate_client =
130      FromWebContents(web_contents);
131  if (!chrome_translate_client)
132    return;
133
134  *source = translate::TranslateDownloadManager::GetLanguageCode(
135      chrome_translate_client->GetLanguageState().original_language());
136
137  Profile* profile =
138      Profile::FromBrowserContext(web_contents->GetBrowserContext());
139  Profile* original_profile = profile->GetOriginalProfile();
140  PrefService* prefs = original_profile->GetPrefs();
141  scoped_ptr<translate::TranslatePrefs> translate_prefs =
142      CreateTranslatePrefs(prefs);
143  if (!web_contents->GetBrowserContext()->IsOffTheRecord()) {
144    std::string auto_translate_language =
145        translate::TranslateManager::GetAutoTargetLanguage(
146            *source, translate_prefs.get());
147    if (!auto_translate_language.empty()) {
148      *target = auto_translate_language;
149      return;
150    }
151  }
152
153  std::string accept_languages_str = prefs->GetString(prefs::kAcceptLanguages);
154  std::vector<std::string> accept_languages_list;
155  base::SplitString(accept_languages_str, ',', &accept_languages_list);
156  *target =
157      translate::TranslateManager::GetTargetLanguage(accept_languages_list);
158}
159
160translate::TranslateManager* ChromeTranslateClient::GetTranslateManager() {
161  return translate_manager_.get();
162}
163
164void ChromeTranslateClient::ShowTranslateUI(
165    translate::TranslateStep step,
166    const std::string source_language,
167    const std::string target_language,
168    translate::TranslateErrors::Type error_type,
169    bool triggered_from_menu) {
170  DCHECK(web_contents());
171  if (error_type != translate::TranslateErrors::NONE)
172    step = translate::TRANSLATE_STEP_TRANSLATE_ERROR;
173
174  if (TranslateService::IsTranslateBubbleEnabled()) {
175    // Bubble UI.
176    if (step == translate::TRANSLATE_STEP_BEFORE_TRANSLATE) {
177      // TODO(droger): Move this logic out of UI code.
178      GetLanguageState().SetTranslateEnabled(true);
179      if (!GetLanguageState().HasLanguageChanged())
180        return;
181
182      if (!triggered_from_menu) {
183        if (web_contents()->GetBrowserContext()->IsOffTheRecord())
184          return;
185        if (GetTranslatePrefs()->IsTooOftenDenied())
186          return;
187      }
188    }
189    ShowBubble(step, error_type);
190    return;
191  }
192
193  // Infobar UI.
194  translate::TranslateInfoBarDelegate::Create(
195      step != translate::TRANSLATE_STEP_BEFORE_TRANSLATE,
196      translate_manager_->GetWeakPtr(),
197      InfoBarService::FromWebContents(web_contents()),
198      web_contents()->GetBrowserContext()->IsOffTheRecord(),
199      step,
200      source_language,
201      target_language,
202      error_type,
203      triggered_from_menu);
204}
205
206translate::TranslateDriver* ChromeTranslateClient::GetTranslateDriver() {
207  return &translate_driver_;
208}
209
210PrefService* ChromeTranslateClient::GetPrefs() {
211  DCHECK(web_contents());
212  Profile* profile =
213      Profile::FromBrowserContext(web_contents()->GetBrowserContext());
214  return profile->GetOriginalProfile()->GetPrefs();
215}
216
217scoped_ptr<translate::TranslatePrefs>
218ChromeTranslateClient::GetTranslatePrefs() {
219  DCHECK(web_contents());
220  Profile* profile =
221      Profile::FromBrowserContext(web_contents()->GetBrowserContext());
222  return CreateTranslatePrefs(profile->GetPrefs());
223}
224
225translate::TranslateAcceptLanguages*
226ChromeTranslateClient::GetTranslateAcceptLanguages() {
227  DCHECK(web_contents());
228  return GetTranslateAcceptLanguages(web_contents()->GetBrowserContext());
229}
230
231int ChromeTranslateClient::GetInfobarIconID() const {
232  return IDR_INFOBAR_TRANSLATE;
233}
234
235// ChromeTranslateClient::CreateInfoBar() is implemented in platform-specific
236// files, except the TOOLKIT_VIEWS implementation, which has been removed. Note
237// for Mac, Cocoa is still providing the infobar in a toolkit-views build.
238#if defined(TOOLKIT_VIEWS) && !defined(OS_MACOSX)
239scoped_ptr<infobars::InfoBar> ChromeTranslateClient::CreateInfoBar(
240    scoped_ptr<translate::TranslateInfoBarDelegate> delegate) const {
241  return scoped_ptr<infobars::InfoBar>();
242}
243#endif
244
245bool ChromeTranslateClient::IsTranslatableURL(const GURL& url) {
246  return TranslateService::IsTranslatableURL(url);
247}
248
249void ChromeTranslateClient::ShowReportLanguageDetectionErrorUI(
250    const GURL& report_url) {
251#if defined(OS_ANDROID)
252  // Android does not support reporting language detection errors.
253  NOTREACHED();
254#else
255  // We'll open the URL in a new tab so that the user can tell us more.
256  Browser* browser = chrome::FindBrowserWithWebContents(web_contents());
257  if (!browser) {
258    NOTREACHED();
259    return;
260  }
261
262  chrome::AddSelectedTabWithURL(
263      browser, report_url, ui::PAGE_TRANSITION_AUTO_BOOKMARK);
264#endif  // defined(OS_ANDROID)
265}
266
267bool ChromeTranslateClient::OnMessageReceived(const IPC::Message& message) {
268  return cld_data_provider_->OnMessageReceived(message);
269}
270
271void ChromeTranslateClient::WebContentsDestroyed() {
272  // Translation process can be interrupted.
273  // Destroying the TranslateManager now guarantees that it never has to deal
274  // with NULL WebContents.
275  translate_manager_.reset();
276}
277
278// ContentTranslateDriver::Observer implementation.
279
280void ChromeTranslateClient::OnLanguageDetermined(
281    const translate::LanguageDetectionDetails& details) {
282  // TODO: Remove translate notifications and have the clients be
283  // ContentTranslateDriver::Observer directly instead.
284  content::NotificationService::current()->Notify(
285      chrome::NOTIFICATION_TAB_LANGUAGE_DETERMINED,
286      content::Source<content::WebContents>(web_contents()),
287      content::Details<const translate::LanguageDetectionDetails>(&details));
288}
289
290void ChromeTranslateClient::OnPageTranslated(
291    const std::string& original_lang,
292    const std::string& translated_lang,
293    translate::TranslateErrors::Type error_type) {
294  // TODO: Remove translate notifications and have the clients be
295  // ContentTranslateDriver::Observer directly instead.
296  DCHECK(web_contents());
297  translate::PageTranslatedDetails details;
298  details.source_language = original_lang;
299  details.target_language = translated_lang;
300  details.error_type = error_type;
301  content::NotificationService::current()->Notify(
302      chrome::NOTIFICATION_PAGE_TRANSLATED,
303      content::Source<content::WebContents>(web_contents()),
304      content::Details<translate::PageTranslatedDetails>(&details));
305}
306
307void ChromeTranslateClient::ShowBubble(
308    translate::TranslateStep step,
309    translate::TranslateErrors::Type error_type) {
310// The bubble is implemented only on the desktop platforms.
311#if !defined(OS_ANDROID) && !defined(OS_IOS)
312  Browser* browser = chrome::FindBrowserWithWebContents(web_contents());
313
314  // |browser| might be NULL when testing. In this case, Show(...) should be
315  // called because the implementation for testing is used.
316  if (!browser) {
317    TranslateBubbleFactory::Show(NULL, web_contents(), step, error_type);
318    return;
319  }
320
321  if (web_contents() != browser->tab_strip_model()->GetActiveWebContents())
322    return;
323
324  // This ShowBubble function is also used for upating the existing bubble.
325  // However, with the bubble shown, any browser windows are NOT activated
326  // because the bubble takes the focus from the other widgets including the
327  // browser windows. So it is checked that |browser| is the last activated
328  // browser, not is now activated.
329  if (browser !=
330      chrome::FindLastActiveWithHostDesktopType(browser->host_desktop_type())) {
331    return;
332  }
333
334  // During auto-translating, the bubble should not be shown.
335  if (step == translate::TRANSLATE_STEP_TRANSLATING ||
336      step == translate::TRANSLATE_STEP_AFTER_TRANSLATE) {
337    if (GetLanguageState().InTranslateNavigation())
338      return;
339  }
340
341  TranslateBubbleFactory::Show(
342      browser->window(), web_contents(), step, error_type);
343#else
344  NOTREACHED();
345#endif
346}
347