| // Copyright 2014 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/translate/ios/browser/ios_translate_driver.h" |
| |
| #include "base/bind.h" |
| #include "base/logging.h" |
| #include "base/strings/sys_string_conversions.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "base/time/time.h" |
| #include "components/translate/core/browser/translate_client.h" |
| #include "components/translate/core/browser/translate_manager.h" |
| #include "components/translate/core/common/translate_constants.h" |
| #include "components/translate/core/common/translate_errors.h" |
| #include "components/translate/core/common/translate_metrics.h" |
| #import "components/translate/ios/browser/js_language_detection_manager.h" |
| #import "components/translate/ios/browser/js_translate_manager.h" |
| #import "components/translate/ios/browser/language_detection_controller.h" |
| #import "components/translate/ios/browser/translate_controller.h" |
| #include "ios/web/public/browser_state.h" |
| #include "ios/web/public/load_committed_details.h" |
| #include "ios/web/public/navigation_item.h" |
| #include "ios/web/public/navigation_manager.h" |
| #include "ios/web/public/referrer.h" |
| #include "ios/web/public/web_state/js/crw_js_injection_receiver.h" |
| #include "ios/web/public/web_state/web_state.h" |
| #include "ui/base/page_transition_types.h" |
| #include "ui/base/window_open_disposition.h" |
| #include "url/gurl.h" |
| |
| #if !defined(__has_feature) || !__has_feature(objc_arc) |
| #error "This file requires ARC support." |
| #endif |
| |
| namespace translate { |
| |
| namespace { |
| // The delay we wait in milliseconds before checking whether the translation has |
| // finished. |
| // Note: This should be kept in sync with the constant of the same name in |
| // translate_ios.js. |
| const int kTranslateStatusCheckDelayMs = 400; |
| // Language name passed to the Translate element for it to detect the language. |
| const char kAutoDetectionLanguage[] = "auto"; |
| |
| } // namespace |
| |
| IOSTranslateDriver::IOSTranslateDriver( |
| web::WebState* web_state, |
| web::NavigationManager* navigation_manager, |
| TranslateManager* translate_manager) |
| : web::WebStateObserver(web_state), |
| navigation_manager_(navigation_manager), |
| translate_manager_(translate_manager->GetWeakPtr()), |
| page_seq_no_(0), |
| pending_page_seq_no_(0), |
| weak_method_factory_(this) { |
| DCHECK(navigation_manager_); |
| DCHECK(translate_manager_); |
| DCHECK(web::WebStateObserver::web_state()); |
| |
| CRWJSInjectionReceiver* receiver = web_state->GetJSInjectionReceiver(); |
| DCHECK(receiver); |
| |
| // Create the language detection controller. |
| JsLanguageDetectionManager* language_detection_manager = |
| static_cast<JsLanguageDetectionManager*>( |
| [receiver instanceOfClass:[JsLanguageDetectionManager class]]); |
| language_detection_controller_.reset(new LanguageDetectionController( |
| web_state, language_detection_manager, |
| translate_manager_->translate_client()->GetPrefs())); |
| language_detection_callback_subscription_ = |
| language_detection_controller_->RegisterLanguageDetectionCallback( |
| base::Bind(&IOSTranslateDriver::OnLanguageDetermined, |
| base::Unretained(this))); |
| // Create the translate controller. |
| JsTranslateManager* js_translate_manager = static_cast<JsTranslateManager*>( |
| [receiver instanceOfClass:[JsTranslateManager class]]); |
| translate_controller_.reset( |
| new TranslateController(web_state, js_translate_manager)); |
| translate_controller_->set_observer(this); |
| } |
| |
| IOSTranslateDriver::~IOSTranslateDriver() { |
| } |
| |
| void IOSTranslateDriver::OnLanguageDetermined( |
| const LanguageDetectionController::DetectionDetails& details) { |
| if (!translate_manager_) |
| return; |
| translate_manager_->GetLanguageState().LanguageDetermined( |
| details.adopted_language, true); |
| |
| if (web_state()) |
| translate_manager_->InitiateTranslation(details.adopted_language); |
| } |
| |
| // web::WebStateObserver methods |
| |
| void IOSTranslateDriver::NavigationItemCommitted( |
| const web::LoadCommittedDetails& load_details) { |
| // Interrupt pending translations and reset various data when a navigation |
| // happens. Desktop does it by tracking changes in the page ID, and |
| // through WebContentObserver, but these concepts do not exist on iOS. |
| if (!load_details.is_in_page) { |
| ++page_seq_no_; |
| translate_manager_->set_current_seq_no(page_seq_no_); |
| } |
| |
| // TODO(droger): support navigation types, like content/ does. |
| const bool reload = ui::PageTransitionCoreTypeIs( |
| load_details.item->GetTransitionType(), ui::PAGE_TRANSITION_RELOAD); |
| translate_manager_->GetLanguageState().DidNavigate(load_details.is_in_page, |
| true, reload); |
| } |
| |
| // TranslateDriver methods |
| |
| bool IOSTranslateDriver::IsLinkNavigation() { |
| return navigation_manager_->GetVisibleItem() && |
| ui::PageTransitionCoreTypeIs( |
| navigation_manager_->GetVisibleItem()->GetTransitionType(), |
| ui::PAGE_TRANSITION_LINK); |
| } |
| |
| void IOSTranslateDriver::OnTranslateEnabledChanged() { |
| } |
| |
| void IOSTranslateDriver::OnIsPageTranslatedChanged() { |
| } |
| |
| void IOSTranslateDriver::TranslatePage(int page_seq_no, |
| const std::string& translate_script, |
| const std::string& source_lang, |
| const std::string& target_lang) { |
| if (page_seq_no != page_seq_no_) |
| return; // The user navigated away. |
| source_language_ = source_lang; |
| target_language_ = target_lang; |
| pending_page_seq_no_ = page_seq_no; |
| translate_controller_->InjectTranslateScript(translate_script); |
| } |
| |
| void IOSTranslateDriver::RevertTranslation(int page_seq_no) { |
| if (page_seq_no != page_seq_no_) |
| return; // The user navigated away. |
| translate_controller_->RevertTranslation(); |
| } |
| |
| bool IOSTranslateDriver::IsOffTheRecord() { |
| return navigation_manager_->GetBrowserState()->IsOffTheRecord(); |
| } |
| |
| const std::string& IOSTranslateDriver::GetContentsMimeType() { |
| return web_state()->GetContentsMimeType(); |
| } |
| |
| const GURL& IOSTranslateDriver::GetLastCommittedURL() { |
| return web_state()->GetLastCommittedURL(); |
| } |
| |
| const GURL& IOSTranslateDriver::GetVisibleURL() { |
| return web_state()->GetVisibleURL(); |
| } |
| |
| bool IOSTranslateDriver::HasCurrentPage() { |
| return (navigation_manager_->GetVisibleItem() != nullptr); |
| } |
| |
| void IOSTranslateDriver::OpenUrlInNewTab(const GURL& url) { |
| web::WebState::OpenURLParams params(url, web::Referrer(), |
| WindowOpenDisposition::NEW_FOREGROUND_TAB, |
| ui::PAGE_TRANSITION_LINK, false); |
| web_state()->OpenURL(params); |
| } |
| |
| void IOSTranslateDriver::TranslationDidSucceed( |
| const std::string& source_lang, |
| const std::string& target_lang, |
| int page_seq_no, |
| const std::string& original_page_language, |
| double translation_time) { |
| if (!IsPageValid(page_seq_no)) |
| return; |
| std::string actual_source_lang; |
| translate::TranslateErrors::Type translate_errors = TranslateErrors::NONE; |
| // Translation was successfull; if it was auto, retrieve the source |
| // language the Translate Element detected. |
| if (source_lang == kAutoDetectionLanguage) { |
| actual_source_lang = original_page_language; |
| if (actual_source_lang.empty()) { |
| translate_errors = TranslateErrors::UNKNOWN_LANGUAGE; |
| } else if (actual_source_lang == target_lang) { |
| translate_errors = TranslateErrors::IDENTICAL_LANGUAGES; |
| } |
| } else { |
| actual_source_lang = source_lang; |
| } |
| if (translate_errors == TranslateErrors::NONE) |
| translate::ReportTimeToTranslate(translation_time); |
| // Notify the manage of completion. |
| translate_manager_->PageTranslated(actual_source_lang, target_lang, |
| translate_errors); |
| } |
| |
| void IOSTranslateDriver::CheckTranslateStatus( |
| const std::string& source_language, |
| const std::string& target_language, |
| int page_seq_no) { |
| if (!IsPageValid(page_seq_no)) |
| return; |
| translate_controller_->CheckTranslateStatus(); |
| } |
| |
| bool IOSTranslateDriver::IsPageValid(int page_seq_no) const { |
| bool user_navigated_away = page_seq_no != page_seq_no_; |
| return !user_navigated_away && web_state(); |
| } |
| |
| // TranslateController::Observer implementation. |
| |
| void IOSTranslateDriver::OnTranslateScriptReady(bool success, |
| double load_time, |
| double ready_time) { |
| if (!IsPageValid(pending_page_seq_no_)) |
| return; |
| |
| if (!success) { |
| translate_manager_->PageTranslated(source_language_, target_language_, |
| TranslateErrors::INITIALIZATION_ERROR); |
| return; |
| } |
| |
| translate::ReportTimeToLoad(load_time); |
| translate::ReportTimeToBeReady(ready_time); |
| const char kAutoDetectionLanguage[] = "auto"; |
| std::string source = (source_language_ != translate::kUnknownLanguageCode) |
| ? source_language_ |
| : kAutoDetectionLanguage; |
| translate_controller_->StartTranslation(source_language_, target_language_); |
| // Check the status of the translation -- after a delay. |
| base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( |
| FROM_HERE, base::Bind(&IOSTranslateDriver::CheckTranslateStatus, |
| weak_method_factory_.GetWeakPtr(), source_language_, |
| target_language_, pending_page_seq_no_), |
| base::TimeDelta::FromMilliseconds(kTranslateStatusCheckDelayMs)); |
| } |
| |
| void IOSTranslateDriver::OnTranslateComplete( |
| bool success, |
| const std::string& original_language, |
| double translation_time) { |
| if (!IsPageValid(pending_page_seq_no_)) |
| return; |
| |
| if (!success) { |
| // TODO(toyoshim): Check |errorCode| of translate.js and notify it here. |
| translate_manager_->PageTranslated(source_language_, target_language_, |
| TranslateErrors::TRANSLATION_ERROR); |
| } |
| |
| TranslationDidSucceed(source_language_, target_language_, |
| pending_page_seq_no_, original_language, |
| translation_time); |
| } |
| |
| } // namespace translate |