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.
5 #include "components/translate/ios/browser/ios_translate_driver.h"
8 #include "base/location.h"
9 #include "base/logging.h"
10 #include "base/message_loop/message_loop.h"
11 #include "base/strings/sys_string_conversions.h"
12 #include "base/time/time.h"
13 #include "components/translate/core/browser/translate_client.h"
14 #include "components/translate/core/browser/translate_manager.h"
15 #include "components/translate/core/common/translate_constants.h"
16 #include "components/translate/core/common/translate_errors.h"
17 #include "components/translate/core/common/translate_metrics.h"
18 #import "components/translate/ios/browser/js_language_detection_manager.h"
19 #import "components/translate/ios/browser/js_translate_manager.h"
20 #import "components/translate/ios/browser/language_detection_controller.h"
21 #import "components/translate/ios/browser/translate_controller.h"
22 #include "ios/web/public/browser_state.h"
23 #include "ios/web/public/load_committed_details.h"
24 #include "ios/web/public/navigation_item.h"
25 #include "ios/web/public/navigation_manager.h"
26 #include "ios/web/public/referrer.h"
27 #include "ios/web/public/web_state/js/crw_js_injection_receiver.h"
28 #include "ios/web/public/web_state/web_state.h"
29 #include "ui/base/page_transition_types.h"
30 #include "ui/base/window_open_disposition.h"
36 // The delay we wait in milliseconds before checking whether the translation has
38 // Note: This should be kept in sync with the constant of the same name in
40 const int kTranslateStatusCheckDelayMs = 400;
41 // Language name passed to the Translate element for it to detect the language.
42 const char kAutoDetectionLanguage[] = "auto";
46 IOSTranslateDriver::IOSTranslateDriver(
47 web::WebState* web_state,
48 web::NavigationManager* navigation_manager,
49 TranslateManager* translate_manager)
50 : web::WebStateObserver(web_state),
51 navigation_manager_(navigation_manager),
52 translate_manager_(translate_manager->GetWeakPtr()),
54 pending_page_seq_no_(0),
55 weak_method_factory_(this) {
56 DCHECK(navigation_manager_);
57 DCHECK(translate_manager_);
58 DCHECK(web::WebStateObserver::web_state());
60 CRWJSInjectionReceiver* receiver = web_state->GetJSInjectionReceiver();
63 // Create the language detection controller.
64 JsLanguageDetectionManager* language_detection_manager =
65 static_cast<JsLanguageDetectionManager*>(
66 [receiver instanceOfClass:[JsLanguageDetectionManager class]]);
67 language_detection_controller_.reset(new LanguageDetectionController(
68 web_state, language_detection_manager,
69 translate_manager_->translate_client()->GetPrefs()));
70 language_detection_callback_subscription_ =
71 language_detection_controller_->RegisterLanguageDetectionCallback(
72 base::Bind(&IOSTranslateDriver::OnLanguageDetermined,
73 base::Unretained(this)));
74 // Create the translate controller.
75 JsTranslateManager* js_translate_manager = static_cast<JsTranslateManager*>(
76 [receiver instanceOfClass:[JsTranslateManager class]]);
77 translate_controller_.reset(
78 new TranslateController(web_state, js_translate_manager));
79 translate_controller_->set_observer(this);
82 IOSTranslateDriver::~IOSTranslateDriver() {
85 void IOSTranslateDriver::OnLanguageDetermined(
86 const LanguageDetectionController::DetectionDetails& details) {
87 if (!translate_manager_)
89 translate_manager_->GetLanguageState().LanguageDetermined(
90 details.adopted_language, true);
93 translate_manager_->InitiateTranslation(details.adopted_language);
96 // web::WebStateObserver methods
98 void IOSTranslateDriver::NavigationItemCommitted(
99 const web::LoadCommittedDetails& load_details) {
100 // Interrupt pending translations and reset various data when a navigation
101 // happens. Desktop does it by tracking changes in the page ID, and
102 // through WebContentObserver, but these concepts do not exist on iOS.
103 if (!load_details.is_in_page) {
105 translate_manager_->set_current_seq_no(page_seq_no_);
108 // TODO(droger): support navigation types, like content/ does.
109 const bool reload = ui::PageTransitionCoreTypeIs(
110 load_details.item->GetTransitionType(), ui::PAGE_TRANSITION_RELOAD);
111 translate_manager_->GetLanguageState().DidNavigate(load_details.is_in_page,
115 // TranslateDriver methods
117 bool IOSTranslateDriver::IsLinkNavigation() {
118 return navigation_manager_->GetVisibleItem() &&
119 ui::PageTransitionCoreTypeIs(
120 navigation_manager_->GetVisibleItem()->GetTransitionType(),
121 ui::PAGE_TRANSITION_LINK);
124 void IOSTranslateDriver::OnTranslateEnabledChanged() {
127 void IOSTranslateDriver::OnIsPageTranslatedChanged() {
130 void IOSTranslateDriver::TranslatePage(int page_seq_no,
131 const std::string& translate_script,
132 const std::string& source_lang,
133 const std::string& target_lang) {
134 if (page_seq_no != page_seq_no_)
135 return; // The user navigated away.
136 source_language_ = source_lang;
137 target_language_ = target_lang;
138 pending_page_seq_no_ = page_seq_no;
139 translate_controller_->InjectTranslateScript(translate_script);
142 void IOSTranslateDriver::RevertTranslation(int page_seq_no) {
143 if (page_seq_no != page_seq_no_)
144 return; // The user navigated away.
145 translate_controller_->RevertTranslation();
148 bool IOSTranslateDriver::IsOffTheRecord() {
149 return navigation_manager_->GetBrowserState()->IsOffTheRecord();
152 const std::string& IOSTranslateDriver::GetContentsMimeType() {
153 return web_state()->GetContentsMimeType();
156 const GURL& IOSTranslateDriver::GetLastCommittedURL() {
157 return web_state()->GetLastCommittedURL();
160 const GURL& IOSTranslateDriver::GetActiveURL() {
161 web::NavigationItem* item = navigation_manager_->GetVisibleItem();
163 return GURL::EmptyGURL();
164 return item->GetURL();
167 const GURL& IOSTranslateDriver::GetVisibleURL() {
168 return web_state()->GetVisibleURL();
171 bool IOSTranslateDriver::HasCurrentPage() {
172 return (navigation_manager_->GetVisibleItem() != nullptr);
175 void IOSTranslateDriver::OpenUrlInNewTab(const GURL& url) {
176 web::WebState::OpenURLParams params(url, web::Referrer(), NEW_FOREGROUND_TAB,
177 ui::PAGE_TRANSITION_LINK, false);
178 web_state()->OpenURL(params);
181 void IOSTranslateDriver::TranslationDidSucceed(
182 const std::string& source_lang,
183 const std::string& target_lang,
185 const std::string& original_page_language,
186 double translation_time) {
187 if (!IsPageValid(page_seq_no))
189 std::string actual_source_lang;
190 translate::TranslateErrors::Type translate_errors = TranslateErrors::NONE;
191 // Translation was successfull; if it was auto, retrieve the source
192 // language the Translate Element detected.
193 if (source_lang == kAutoDetectionLanguage) {
194 actual_source_lang = original_page_language;
195 if (actual_source_lang.empty()) {
196 translate_errors = TranslateErrors::UNKNOWN_LANGUAGE;
197 } else if (actual_source_lang == target_lang) {
198 translate_errors = TranslateErrors::IDENTICAL_LANGUAGES;
201 actual_source_lang = source_lang;
203 if (translate_errors == TranslateErrors::NONE)
204 translate::ReportTimeToTranslate(translation_time);
205 // Notify the manage of completion.
206 translate_manager_->PageTranslated(actual_source_lang, target_lang,
210 void IOSTranslateDriver::CheckTranslateStatus(
211 const std::string& source_language,
212 const std::string& target_language,
214 if (!IsPageValid(page_seq_no))
216 translate_controller_->CheckTranslateStatus();
219 bool IOSTranslateDriver::IsPageValid(int page_seq_no) const {
220 bool user_navigated_away = page_seq_no != page_seq_no_;
221 return !user_navigated_away && web_state();
224 // TranslateController::Observer implementation.
226 void IOSTranslateDriver::OnTranslateScriptReady(bool success,
229 if (!IsPageValid(pending_page_seq_no_))
233 translate_manager_->PageTranslated(source_language_, target_language_,
234 TranslateErrors::INITIALIZATION_ERROR);
238 translate::ReportTimeToLoad(load_time);
239 translate::ReportTimeToBeReady(ready_time);
240 const char kAutoDetectionLanguage[] = "auto";
241 std::string source = (source_language_ != translate::kUnknownLanguageCode)
243 : kAutoDetectionLanguage;
244 translate_controller_->StartTranslation(source_language_, target_language_);
245 // Check the status of the translation -- after a delay.
246 base::MessageLoop::current()->PostDelayedTask(
247 FROM_HERE, base::Bind(&IOSTranslateDriver::CheckTranslateStatus,
248 weak_method_factory_.GetWeakPtr(), source_language_,
249 target_language_, pending_page_seq_no_),
250 base::TimeDelta::FromMilliseconds(kTranslateStatusCheckDelayMs));
253 void IOSTranslateDriver::OnTranslateComplete(
255 const std::string& original_language,
256 double translation_time) {
257 if (!IsPageValid(pending_page_seq_no_))
261 // TODO(toyoshim): Check |errorCode| of translate.js and notify it here.
262 translate_manager_->PageTranslated(source_language_, target_language_,
263 TranslateErrors::TRANSLATION_ERROR);
266 TranslationDidSucceed(source_language_, target_language_,
267 pending_page_seq_no_, original_language,
271 } // namespace translate