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/content/browser/content_translate_driver.h"
6
7#include "base/bind.h"
8#include "base/logging.h"
9#include "components/translate/content/common/translate_messages.h"
10#include "components/translate/core/browser/translate_download_manager.h"
11#include "components/translate/core/browser/translate_manager.h"
12#include "content/public/browser/browser_context.h"
13#include "content/public/browser/navigation_controller.h"
14#include "content/public/browser/navigation_details.h"
15#include "content/public/browser/navigation_entry.h"
16#include "content/public/browser/page_navigator.h"
17#include "content/public/browser/render_view_host.h"
18#include "content/public/browser/web_contents.h"
19#include "content/public/common/referrer.h"
20#include "net/http/http_status_code.h"
21#include "url/gurl.h"
22
23namespace {
24
25// The maximum number of attempts we'll do to see if the page has finshed
26// loading before giving up the translation
27const int kMaxTranslateLoadCheckAttempts = 20;
28
29}  // namespace
30
31namespace translate {
32
33ContentTranslateDriver::ContentTranslateDriver(
34    content::NavigationController* nav_controller)
35    : content::WebContentsObserver(nav_controller->GetWebContents()),
36      navigation_controller_(nav_controller),
37      translate_manager_(NULL),
38      max_reload_check_attempts_(kMaxTranslateLoadCheckAttempts),
39      weak_pointer_factory_(this) {
40  DCHECK(navigation_controller_);
41}
42
43ContentTranslateDriver::~ContentTranslateDriver() {}
44
45void ContentTranslateDriver::AddObserver(Observer* observer) {
46  observer_list_.AddObserver(observer);
47}
48
49void ContentTranslateDriver::RemoveObserver(Observer* observer) {
50  observer_list_.RemoveObserver(observer);
51}
52
53void ContentTranslateDriver::InitiateTranslation(const std::string& page_lang,
54                                                 int attempt) {
55  if (translate_manager_->GetLanguageState().translation_pending())
56    return;
57
58  // During a reload we need web content to be available before the
59  // translate script is executed. Otherwise we will run the translate script on
60  // an empty DOM which will fail. Therefore we wait a bit to see if the page
61  // has finished.
62  if (web_contents()->IsLoading() && attempt < max_reload_check_attempts_) {
63    int backoff = attempt * kMaxTranslateLoadCheckAttempts;
64    base::MessageLoop::current()->PostDelayedTask(
65        FROM_HERE,
66        base::Bind(&ContentTranslateDriver::InitiateTranslation,
67                   weak_pointer_factory_.GetWeakPtr(),
68                   page_lang,
69                   attempt + 1),
70        base::TimeDelta::FromMilliseconds(backoff));
71    return;
72  }
73
74  translate_manager_->InitiateTranslation(
75      translate::TranslateDownloadManager::GetLanguageCode(page_lang));
76}
77
78// TranslateDriver methods
79
80bool ContentTranslateDriver::IsLinkNavigation() {
81  return navigation_controller_ && navigation_controller_->GetActiveEntry() &&
82         navigation_controller_->GetActiveEntry()->GetTransitionType() ==
83             ui::PAGE_TRANSITION_LINK;
84}
85
86void ContentTranslateDriver::OnTranslateEnabledChanged() {
87  content::WebContents* web_contents = navigation_controller_->GetWebContents();
88  FOR_EACH_OBSERVER(
89      Observer, observer_list_, OnTranslateEnabledChanged(web_contents));
90}
91
92void ContentTranslateDriver::OnIsPageTranslatedChanged() {
93    content::WebContents* web_contents =
94        navigation_controller_->GetWebContents();
95    FOR_EACH_OBSERVER(
96        Observer, observer_list_, OnIsPageTranslatedChanged(web_contents));
97}
98
99void ContentTranslateDriver::TranslatePage(int page_seq_no,
100                                           const std::string& translate_script,
101                                           const std::string& source_lang,
102                                           const std::string& target_lang) {
103  content::WebContents* web_contents = navigation_controller_->GetWebContents();
104  web_contents->GetRenderViewHost()->Send(
105      new ChromeViewMsg_TranslatePage(
106          web_contents->GetRenderViewHost()->GetRoutingID(),
107          page_seq_no,
108          translate_script,
109          source_lang,
110          target_lang));
111}
112
113void ContentTranslateDriver::RevertTranslation(int page_seq_no) {
114  content::WebContents* web_contents = navigation_controller_->GetWebContents();
115  web_contents->GetRenderViewHost()->Send(
116      new ChromeViewMsg_RevertTranslation(
117          web_contents->GetRenderViewHost()->GetRoutingID(),
118          page_seq_no));
119}
120
121bool ContentTranslateDriver::IsOffTheRecord() {
122  return navigation_controller_->GetBrowserContext()->IsOffTheRecord();
123}
124
125const std::string& ContentTranslateDriver::GetContentsMimeType() {
126  return navigation_controller_->GetWebContents()->GetContentsMimeType();
127}
128
129const GURL& ContentTranslateDriver::GetLastCommittedURL() {
130  return navigation_controller_->GetWebContents()->GetLastCommittedURL();
131}
132
133const GURL& ContentTranslateDriver::GetActiveURL() {
134  content::NavigationEntry* entry = navigation_controller_->GetActiveEntry();
135  if (!entry)
136    return GURL::EmptyGURL();
137  return entry->GetURL();
138}
139
140const GURL& ContentTranslateDriver::GetVisibleURL() {
141  return navigation_controller_->GetWebContents()->GetVisibleURL();
142}
143
144bool ContentTranslateDriver::HasCurrentPage() {
145  return (navigation_controller_->GetActiveEntry() != NULL);
146}
147
148void ContentTranslateDriver::OpenUrlInNewTab(const GURL& url) {
149  content::OpenURLParams params(url,
150                                content::Referrer(),
151                                NEW_FOREGROUND_TAB,
152                                ui::PAGE_TRANSITION_LINK,
153                                false);
154  navigation_controller_->GetWebContents()->OpenURL(params);
155}
156
157// content::WebContentsObserver methods
158
159void ContentTranslateDriver::NavigationEntryCommitted(
160    const content::LoadCommittedDetails& load_details) {
161  // Check whether this is a reload: When doing a page reload, the
162  // TranslateLanguageDetermined IPC is not sent so the translation needs to be
163  // explicitly initiated.
164
165  content::NavigationEntry* entry =
166      web_contents()->GetController().GetActiveEntry();
167  if (!entry) {
168    NOTREACHED();
169    return;
170  }
171
172  // If the navigation happened while offline don't show the translate
173  // bar since there will be nothing to translate.
174  if (load_details.http_status_code == 0 ||
175      load_details.http_status_code == net::HTTP_INTERNAL_SERVER_ERROR) {
176    return;
177  }
178
179  if (!load_details.is_main_frame &&
180      translate_manager_->GetLanguageState().translation_declined()) {
181    // Some sites (such as Google map) may trigger sub-frame navigations
182    // when the user interacts with the page.  We don't want to show a new
183    // infobar if the user already dismissed one in that case.
184    return;
185  }
186
187  // If not a reload, return.
188  if (entry->GetTransitionType() != ui::PAGE_TRANSITION_RELOAD &&
189      load_details.type != content::NAVIGATION_TYPE_SAME_PAGE) {
190    return;
191  }
192
193  if (!translate_manager_->GetLanguageState().page_needs_translation())
194    return;
195
196  // Note that we delay it as the ordering of the processing of this callback
197  // by WebContentsObservers is undefined and might result in the current
198  // infobars being removed. Since the translation initiation process might add
199  // an infobar, it must be done after that.
200  base::MessageLoop::current()->PostTask(
201      FROM_HERE,
202      base::Bind(&ContentTranslateDriver::InitiateTranslation,
203                 weak_pointer_factory_.GetWeakPtr(),
204                 translate_manager_->GetLanguageState().original_language(),
205                 0));
206}
207
208void ContentTranslateDriver::DidNavigateAnyFrame(
209    const content::LoadCommittedDetails& details,
210    const content::FrameNavigateParams& params) {
211  // Let the LanguageState clear its state.
212  const bool reload =
213      details.entry->GetTransitionType() == ui::PAGE_TRANSITION_RELOAD ||
214      details.type == content::NAVIGATION_TYPE_SAME_PAGE;
215  translate_manager_->GetLanguageState().DidNavigate(
216      details.is_in_page, details.is_main_frame, reload);
217}
218
219bool ContentTranslateDriver::OnMessageReceived(const IPC::Message& message) {
220  bool handled = true;
221  IPC_BEGIN_MESSAGE_MAP(ContentTranslateDriver, message)
222  IPC_MESSAGE_HANDLER(ChromeViewHostMsg_TranslateAssignedSequenceNumber,
223                      OnTranslateAssignedSequenceNumber)
224  IPC_MESSAGE_HANDLER(ChromeViewHostMsg_TranslateLanguageDetermined,
225                      OnLanguageDetermined)
226  IPC_MESSAGE_HANDLER(ChromeViewHostMsg_PageTranslated, OnPageTranslated)
227  IPC_MESSAGE_UNHANDLED(handled = false)
228  IPC_END_MESSAGE_MAP()
229  return handled;
230}
231
232void ContentTranslateDriver::OnTranslateAssignedSequenceNumber(
233    int page_seq_no) {
234  translate_manager_->set_current_seq_no(page_seq_no);
235}
236
237void ContentTranslateDriver::OnLanguageDetermined(
238    const LanguageDetectionDetails& details,
239    bool page_needs_translation) {
240  translate_manager_->GetLanguageState().LanguageDetermined(
241      details.adopted_language, page_needs_translation);
242
243  if (web_contents())
244    translate_manager_->InitiateTranslation(details.adopted_language);
245
246  FOR_EACH_OBSERVER(Observer, observer_list_, OnLanguageDetermined(details));
247}
248
249void ContentTranslateDriver::OnPageTranslated(
250    const std::string& original_lang,
251    const std::string& translated_lang,
252    TranslateErrors::Type error_type) {
253  translate_manager_->PageTranslated(
254      original_lang, translated_lang, error_type);
255  FOR_EACH_OBSERVER(
256      Observer,
257      observer_list_,
258      OnPageTranslated(original_lang, translated_lang, error_type));
259}
260
261}  // namespace translate
262