diff options
Diffstat (limited to 'chrome/browser')
-rw-r--r-- | chrome/browser/browser_resources.grd | 1 | ||||
-rw-r--r-- | chrome/browser/renderer_host/render_view_host.cc | 7 | ||||
-rw-r--r-- | chrome/browser/renderer_host/render_view_host.h | 6 | ||||
-rw-r--r-- | chrome/browser/renderer_host/resource_message_filter.cc | 9 | ||||
-rw-r--r-- | chrome/browser/renderer_host/resource_message_filter.h | 6 | ||||
-rw-r--r-- | chrome/browser/renderer_host/translation_service.cc | 675 | ||||
-rw-r--r-- | chrome/browser/renderer_host/translation_service.h | 195 | ||||
-rw-r--r-- | chrome/browser/renderer_host/translation_service_unittest.cc | 505 | ||||
-rw-r--r-- | chrome/browser/resources/translate.js | 111 | ||||
-rw-r--r-- | chrome/browser/tab_contents/render_view_context_menu.cc | 20 | ||||
-rw-r--r-- | chrome/browser/tab_contents/tab_contents.cc | 17 | ||||
-rw-r--r-- | chrome/browser/tab_contents/tab_contents.h | 7 | ||||
-rw-r--r-- | chrome/browser/translate/translate_infobars_delegates.cc | 20 | ||||
-rw-r--r-- | chrome/browser/translate/translate_manager.cc | 253 | ||||
-rw-r--r-- | chrome/browser/translate/translate_manager.h | 77 | ||||
-rw-r--r-- | chrome/browser/translate/translate_manager_unittest.cc | 74 |
16 files changed, 514 insertions, 1469 deletions
diff --git a/chrome/browser/browser_resources.grd b/chrome/browser/browser_resources.grd index 1aeceab..fe9f9e9 100644 --- a/chrome/browser/browser_resources.grd +++ b/chrome/browser/browser_resources.grd @@ -66,6 +66,7 @@ without changes to the corresponding grd file. fbt1 --> <include name="IDR_NOTIFICATION_ICON_HTML" file="resources\notification_icon.html" type="BINDATA" /> <include name="IDR_NOTIFICATION_2LINE_HTML" file="resources\notification_2line.html" type="BINDATA" /> <include name="IDR_NOTIFICATION_1LINE_HTML" file="resources\notification_1line.html" type="BINDATA" /> + <include name="IDR_TRANSLATE_JS" file="resources\translate.js" type="BINDATA" /> </includes> </release> </grit> diff --git a/chrome/browser/renderer_host/render_view_host.cc b/chrome/browser/renderer_host/render_view_host.cc index 47debf7..3697719 100644 --- a/chrome/browser/renderer_host/render_view_host.cc +++ b/chrome/browser/renderer_host/render_view_host.cc @@ -1789,12 +1789,17 @@ void RenderViewHost::PerformCustomContextMenuAction(unsigned action) { } void RenderViewHost::TranslatePage(int page_id, + const std::string& translate_script, const std::string& source_lang, const std::string& target_lang) { - Send(new ViewMsg_TranslatePage(routing_id(), page_id, + Send(new ViewMsg_TranslatePage(routing_id(), page_id, translate_script, source_lang, target_lang)); } +void RenderViewHost::RevertTranslation(int page_id) { + Send(new ViewMsg_RevertTranslation(routing_id(), page_id)); +} + void RenderViewHost::SendContentSettings(const std::string& host, const ContentSettings& settings) { Send(new ViewMsg_SetContentSettingsForCurrentHost(host, settings)); diff --git a/chrome/browser/renderer_host/render_view_host.h b/chrome/browser/renderer_host/render_view_host.h index e35a707..48300d9 100644 --- a/chrome/browser/renderer_host/render_view_host.h +++ b/chrome/browser/renderer_host/render_view_host.h @@ -442,10 +442,16 @@ class RenderViewHost : public RenderWidgetHost { // Tells the renderer to translate the current page from one language to // another. If the current page id is not |page_id|, the request is ignored. + // |translate_script| is the script that should be injected in the page to + // perform the translation. void TranslatePage(int page_id, + const std::string& translate_script, const std::string& source_lang, const std::string& target_lang); + // Reverts the text of current page to its original (non-translated) contents. + void RevertTranslation(int page_id); + // Informs renderer of updated content settings. void SendContentSettings(const std::string& host, const ContentSettings& settings); diff --git a/chrome/browser/renderer_host/resource_message_filter.cc b/chrome/browser/renderer_host/resource_message_filter.cc index a335889..0657913 100644 --- a/chrome/browser/renderer_host/resource_message_filter.cc +++ b/chrome/browser/renderer_host/resource_message_filter.cc @@ -315,7 +315,6 @@ ResourceMessageFilter::ResourceMessageFilter( off_the_record_(profile->IsOffTheRecord()), next_route_id_callback_(NewCallbackWithReturnValue( render_widget_helper, &RenderWidgetHelper::GetNextRoutingID)), - ALLOW_THIS_IN_INITIALIZER_LIST(translation_service_(this)), ALLOW_THIS_IN_INITIALIZER_LIST(geolocation_dispatcher_host_( new GeolocationDispatcherHost( this->id(), new GeolocationPermissionContext(profile)))) { @@ -538,7 +537,6 @@ bool ResourceMessageFilter::OnMessageReceived(const IPC::Message& msg) { IPC_MESSAGE_HANDLER(ViewHostMsg_Keygen, OnKeygen) IPC_MESSAGE_HANDLER_DELAY_REPLY(ViewHostMsg_GetExtensionMessageBundle, OnGetExtensionMessageBundle) - IPC_MESSAGE_HANDLER(ViewHostMsg_TranslateText, OnTranslateText) #if defined(USE_TCMALLOC) IPC_MESSAGE_HANDLER(ViewHostMsg_RendererTcmalloc, OnRendererTcmalloc) #endif @@ -1404,13 +1402,6 @@ void ResourceMessageFilter::OnKeygen(uint32 key_size_index, *signed_public_key = keygen_handler.GenKeyAndSignChallenge(); } -void ResourceMessageFilter::OnTranslateText( - ViewHostMsg_TranslateTextParam param) { - translation_service_.Translate(param.routing_id, param.page_id, param.work_id, - param.text_chunks, param.from_language, - param.to_language, param.secure); -} - #if defined(USE_TCMALLOC) void ResourceMessageFilter::OnRendererTcmalloc(base::ProcessId pid, const std::string& output) { diff --git a/chrome/browser/renderer_host/resource_message_filter.h b/chrome/browser/renderer_host/resource_message_filter.h index 036ac7f..20be786 100644 --- a/chrome/browser/renderer_host/resource_message_filter.h +++ b/chrome/browser/renderer_host/resource_message_filter.h @@ -24,7 +24,6 @@ #include "build/build_config.h" #include "chrome/browser/net/resolve_proxy_msg_helper.h" #include "chrome/browser/renderer_host/resource_dispatcher_host.h" -#include "chrome/browser/renderer_host/translation_service.h" #include "chrome/common/nacl_types.h" #include "chrome/common/notification_registrar.h" #include "chrome/common/render_messages.h" @@ -324,8 +323,6 @@ class ResourceMessageFilter : public IPC::ChannelProxy::MessageFilter, const std::string& extension_id, const std::string& default_locale, IPC::Message* reply_msg); - void OnTranslateText(ViewHostMsg_TranslateTextParam param); - void OnEstablishGpuChannel(); void OnSynchronizeGpu(IPC::Message* reply); @@ -412,9 +409,6 @@ class ResourceMessageFilter : public IPC::ChannelProxy::MessageFilter, // A callback to create a routing id for the associated renderer process. scoped_ptr<CallbackWithReturnValue<int>::Type> next_route_id_callback_; - // Used to translate page contents from one language to another. - TranslationService translation_service_; - // Used to handle geolocation-related messages. scoped_refptr<GeolocationDispatcherHost> geolocation_dispatcher_host_; diff --git a/chrome/browser/renderer_host/translation_service.cc b/chrome/browser/renderer_host/translation_service.cc deleted file mode 100644 index 153352b..0000000 --- a/chrome/browser/renderer_host/translation_service.cc +++ /dev/null @@ -1,675 +0,0 @@ -// Copyright (c) 2010 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 "chrome/browser/renderer_host/translation_service.h" - -#include "base/json/json_reader.h" -#include "base/stl_util-inl.h" -#include "chrome/browser/profile.h" -#include "chrome/common/render_messages.h" -#include "net/base/escape.h" - -#if defined(GOOGLE_CHROME_BUILD) -#include "chrome/browser/renderer_host/translate/translate_internal.h" -#else -// Defining dummy URLs for unit-tests to pass. -#define TRANSLATE_SERVER_URL "http://disabled" -#define TRANSLATE_SERVER_SECURE_URL "https://disabled" -#endif - -namespace { - -// The URLs we send translation requests to. -const char kServiceURL[] = TRANSLATE_SERVER_URL; -const char kSecureServiceURL[] = TRANSLATE_SERVER_SECURE_URL; - -// The different params used when sending requests to the translate server. -const char kVersionParam[] = "v"; -const char kLangPairParam[] = "langpair"; -const char kTextParam[] = "q"; -const char kClientParam[] = "client"; -const char kFormatParam[] = "format"; -const char kSSLParam[] = "ssl"; -const char kTranslationCountParam[] = "tc"; - -// Mapping from a locale name to a language code name. -// Locale names not included are translated as is. -struct LocaleToCLDLanguage { - const char* locale_language; // Language Chrome locale is in. - const char* cld_language; // Language the CLD reports. -}; -LocaleToCLDLanguage kLocaleToCLDLanguages[] = { - { "en-GB", "en" }, - { "en-US", "en" }, - { "es-419", "es" }, - { "pt-BR", "pt" }, - { "pt-PT", "pt" }, -}; - -// The list of languages the Google translation server supports. -// For information, here is the list of languages that Chrome can be run into -// but that the translation server does not support: -// am Amharic -// bn Bengali -// gu Gujarati -// kn Kannada -// ml Malayalam -// mr Marathi -// ta Tamil -// te Telugu -const char* kSupportedLanguages[] = { - "af", // Afrikaans - "sq", // Albanian - "ar", // Arabic - "be", // Belarusian - "bg", // Bulgarian - "ca", // Catalan - "zh-CN", // Chinese (Simplified) - "zh-TW", // Chinese (Traditional) - "hr", // Croatian - "cs", // Czech - "da", // Danish - "nl", // Dutch - "en", // English - "et", // Estonian - "fi", // Finnish - "fil", // Filipino - "fr", // French - "gl", // Galician - "de", // German - "el", // Greek - "he", // Hebrew - "hi", // Hindi - "hu", // Hungarian - "is", // Icelandic - "id", // Indonesian - "it", // Italian - "ga", // Irish - "ja", // Japanese - "ko", // Korean - "lv", // Latvian - "lt", // Lithuanian - "mk", // Macedonian - "ms", // Malay - "mt", // Maltese - "nb", // Norwegian - "fa", // Persian - "pl", // Polish - "pt", // Portuguese - "ro", // Romanian - "ru", // Russian - "sr", // Serbian - "sk", // Slovak - "sl", // Slovenian - "es", // Spanish - "sw", // Swahili - "sv", // Swedish - "th", // Thai - "tr", // Turkish - "uk", // Ukrainian - "vi", // Vietnamese - "cy", // Welsh - "yi", // Yiddish -}; - -// The maximum size in bytes after which the server will refuse the request. -const size_t kTextRequestMaxSize = 1024 * 30; - -// Delay to wait for before sending a request to the translation server. -const int kSendRequestDelay = 100; - -// Task used to send the current pending translation request for a renderer -// after some time has elapsed with no new request from that renderer. -// Note that this task is canceled when TranslationRequest is destroyed, which -// happens when the TranslationService is going away. So it is OK to have it -// have a pointer to the TranslationService. -class SendTranslationRequestTask : public CancelableTask { - public: - SendTranslationRequestTask(TranslationService* translation_service, - int renderer_id, - bool secure); - virtual void Run(); - virtual void Cancel(); - - private: - TranslationService* translation_service_; - int renderer_id_; - bool secure_; - bool canceled_; - - DISALLOW_COPY_AND_ASSIGN(SendTranslationRequestTask); -}; - -} // namespace - -// static -// The string is: '&' + kTextParam + '='. -size_t TranslationService::text_param_length_ = 1 + arraysize(kTextParam) + 1; - -// static -base::LazyInstance<std::set<std::string> > - TranslationService::supported_languages_(base::LINKER_INITIALIZED); - -// Contains the information necessary to send a request to the translation -// server. It is used to group several renderer queries, as to limit the -// load sent to the translation server. -struct TranslationService::TranslationRequest { - TranslationRequest(int routing_id, - int page_id, - const std::string& source_lang, - const std::string& target_lang, - bool secure) - : routing_id(routing_id), - page_id(page_id), - source_lang(source_lang), - target_lang(target_lang), - secure(secure), - send_query_task(NULL) { - renderer_request_info.reset(new RendererRequestInfoList()); - } - - ~TranslationRequest() { - if (send_query_task) - send_query_task->Cancel(); - } - - void Clear() { - page_id = 0; - source_lang.clear(); - target_lang.clear(); - query.clear(); - renderer_request_info->clear(); - if (send_query_task) { - send_query_task->Cancel(); - send_query_task = NULL; - } - } - - int routing_id; - int page_id; - std::string source_lang; - std::string target_lang; - bool secure; - std::string query; - // renderer_request_info is a scoped_ptr so that we avoid copying the list - // when the request is sent. At that point we only transfer ownership of that - // list to renderer_request_infos_. - scoped_ptr<RendererRequestInfoList> renderer_request_info; - CancelableTask* send_query_task; -}; - -//////////////////////////////////////////////////////////////////////////////// -// SendTranslationRequestTask - -SendTranslationRequestTask::SendTranslationRequestTask( - TranslationService* translation_service, - int renderer_id, - bool secure) - : translation_service_(translation_service), - renderer_id_(renderer_id), - secure_(secure), - canceled_(false) { -} - -void SendTranslationRequestTask::Run() { - if (canceled_) - return; - translation_service_-> - SendTranslationRequestForRenderer(renderer_id_, secure_); -} - -void SendTranslationRequestTask::Cancel() { - canceled_ = true; -} - -//////////////////////////////////////////////////////////////////////////////// -// TranslationService, public: - -TranslationService::TranslationService(IPC::Message::Sender* message_sender) - : message_sender_(message_sender), - kCRAnchorTagStart(ASCIIToUTF16("<a _CR_TR_ id='")), - kAnchorTagStart(ASCIIToUTF16("<a ")), - kClosingAnchorTag(ASCIIToUTF16("</a>")), - kQuote(ASCIIToUTF16("'")), - kGreaterThan(ASCIIToUTF16(">")), - kLessThan(ASCIIToUTF16("<")), - kQuoteGreaterThan(ASCIIToUTF16("'>")) { -} - -TranslationService::~TranslationService() { - STLDeleteContainerPairSecondPointers(pending_translation_requests_.begin(), - pending_translation_requests_.end()); - STLDeleteContainerPairSecondPointers( - pending_secure_translation_requests_.begin(), - pending_secure_translation_requests_.end()); - STLDeleteContainerPairPointers(renderer_request_infos_.begin(), - renderer_request_infos_.end()); -} - -void TranslationService::Translate(int routing_id, - int page_id, - int work_id, - const TextChunks& text_chunks, - const std::string& source_lang, - const std::string& target_lang, - bool secure) { - TranslationRequestMap& request_map = - secure ? pending_secure_translation_requests_ : - pending_translation_requests_; - TranslationRequestMap::iterator iter = request_map.find(routing_id); - TranslationRequest* translation_request = NULL; - - string16 utf16_text = MergeTextChunks(text_chunks); - std::string text = EscapeUrlEncodedData(UTF16ToUTF8(utf16_text)); - - if (iter != request_map.end()) { - translation_request = iter->second; - if (page_id != translation_request->page_id) { - // We are getting a request from a renderer for a different page id. - // This indicates we navigated away from the page that was being - // translated. We should drop the current pending translations. - translation_request->Clear(); - // Set the new states. - translation_request->page_id = page_id; - translation_request->source_lang = source_lang; - translation_request->target_lang = target_lang; - } else { - DCHECK(translation_request->source_lang == source_lang); - DCHECK(translation_request->target_lang == target_lang); - // Cancel the pending tasks to send the query. We'll be posting a new one - // after we updated the request. - translation_request->send_query_task->Cancel(); - translation_request->send_query_task = NULL; - if (translation_request->query.size() + text.size() + - text_param_length_ >= kTextRequestMaxSize) { - // The request would be too big with that last addition of text, send - // the request now. (Single requests too big to be sent in 1 translation - // request are dealt with below.) - if (!translation_request->query.empty()) { // Single requests - SendRequestToTranslationServer(translation_request); - // The translation request has been deleted. - translation_request = NULL; - iter = request_map.end(); - } - } - } - } - - if (translation_request == NULL) { - translation_request = new TranslationRequest(routing_id, page_id, - source_lang, target_lang, - secure); - request_map[routing_id] = translation_request; - } - - AddTextToRequestString(&(translation_request->query), text, - source_lang, target_lang, secure); - - translation_request->renderer_request_info->push_back( - RendererRequestInfo(routing_id, work_id)); - - if (translation_request->query.size() > kTextRequestMaxSize) { - DCHECK(translation_request->renderer_request_info->size() == 1U); - // This one request is too large for the translation service. - // TODO(jcampan): we should support such requests by splitting them. - iter = request_map.find(routing_id); - DCHECK(iter != request_map.end()); - request_map.erase(iter); - message_sender_->Send( - new ViewMsg_TranslateTextReponse(routing_id, work_id, 1, TextChunks())); - delete translation_request; - return; - } - - // Now post the new task that will ensure we'll send the request to the - // translation server if no renderer requests are received within a - // reasonable amount of time. - DCHECK(!translation_request->send_query_task); - translation_request->send_query_task = - new SendTranslationRequestTask(this, routing_id, secure); - MessageLoop::current()->PostDelayedTask(FROM_HERE, - translation_request->send_query_task, GetSendRequestDelay()); -} - -void TranslationService::SendTranslationRequestForRenderer(int renderer_id, - bool secure) { - TranslationRequestMap& request_map = - secure ? pending_secure_translation_requests_ : - pending_translation_requests_; - TranslationRequestMap::const_iterator iter = request_map.find(renderer_id); - DCHECK(iter != request_map.end()); - SendRequestToTranslationServer(iter->second); -} - -void TranslationService::OnURLFetchComplete(const URLFetcher* source, - const GURL& url, - const URLRequestStatus& status, - int response_code, - const ResponseCookies& cookies, - const std::string& data) { - if (!status.is_success() || response_code != 200 || data.empty()) { - TranslationFailed(source); - return; - } - - // If the response is a simple string, put it in an array. (The JSONReader - // requires an array or map at the root.) - std::string wrapped_data; - if (data.size() > 1U && data[0] == '"') { - wrapped_data.append("["); - wrapped_data.append(data); - wrapped_data.append("]"); - } - scoped_ptr<Value> value(base::JSONReader::Read( - wrapped_data.empty() ? data : wrapped_data, true)); - if (!value.get()) { - NOTREACHED() << "Translation server returned invalid JSON response."; - TranslationFailed(source); - return; - } - - // If the request was for a single string, the response is the translated - // string. - TextChunksList translated_chunks_list; - if (value->IsType(Value::TYPE_STRING)) { - string16 translated_text; - if (!value->GetAsUTF16(&translated_text)) { - NOTREACHED(); - TranslationFailed(source); - return; - } - TextChunks translated_text_chunks; - translated_text_chunks.push_back(translated_text); - translated_chunks_list.push_back(translated_text_chunks); - } else { - if (!value->IsType(Value::TYPE_LIST)) { - NOTREACHED() << "Translation server returned unexpected JSON response " - " (not a list)."; - TranslationFailed(source); - return; - } - ListValue* translated_text_list = static_cast<ListValue*>(value.get()); - for (size_t i = 0; i < translated_text_list->GetSize(); ++i) { - string16 translated_text; - if (!translated_text_list->GetStringAsUTF16(i, &translated_text)) { - NOTREACHED() << "Translation server returned unexpected JSON response " - " (unexpected type in list)."; - TranslationFailed(source); - return; - } - translated_text = UnescapeForHTML(translated_text); - TranslationService::TextChunks translated_text_chunks; - TranslationService::SplitIntoTextChunks(translated_text, - &translated_text_chunks); - translated_chunks_list.push_back(translated_text_chunks); - } - } - - // We have successfully extracted all the translated text chunks, send them to - // the renderer. - SendResponseToRenderer(source, 0, translated_chunks_list); -} - -// static -bool TranslationService::IsTranslationEnabled() { - return GURL(kServiceURL).host() != "disabled"; -} - -// static -void TranslationService::GetSupportedLanguages( - std::vector<std::string>* languages) { - DCHECK(languages && languages->empty()); - for (size_t i = 0; i < arraysize(kSupportedLanguages); ++i) - languages->push_back(kSupportedLanguages[i]); -} - -// static -std::string TranslationService::GetLanguageCode( - const std::string& chrome_locale) { - for (size_t i = 0; i < arraysize(kLocaleToCLDLanguages); ++i) { - if (chrome_locale == kLocaleToCLDLanguages[i].locale_language) - return kLocaleToCLDLanguages[i].cld_language; - } - return chrome_locale; -} - -// static -bool TranslationService::IsSupportedLanguage(const std::string& page_language) { - if (supported_languages_.Pointer()->empty()) { - for (size_t i = 0; i < arraysize(kSupportedLanguages); ++i) - supported_languages_.Pointer()->insert(kSupportedLanguages[i]); - } - return supported_languages_.Pointer()->find(page_language) != - supported_languages_.Pointer()->end(); -} - -//////////////////////////////////////////////////////////////////////////////// -// TranslationService, protected: - -int TranslationService::GetSendRequestDelay() const { - return kSendRequestDelay; -} - -//////////////////////////////////////////////////////////////////////////////// -// TranslationService, private: - -void TranslationService::SendRequestToTranslationServer( - TranslationRequest* request) { - DCHECK(!request->query.empty()); - GURL url(request->secure ? kSecureServiceURL : kServiceURL); - URLFetcher* url_fetcher = - URLFetcher::Create(request->routing_id /* used in tests */, - url, URLFetcher::POST, this); - url_fetcher->set_upload_data("application/x-www-form-urlencoded", - request->query); - url_fetcher->set_request_context(Profile::GetDefaultRequestContext()); - url_fetcher->Start(); - - // renderer_request_infos_ will now own the RendererRequestInfoList. - renderer_request_infos_[url_fetcher] = - request->renderer_request_info.release(); - - // Remove the request from the translation request map. - TranslationRequestMap& translation_request_map = - request->secure ? pending_secure_translation_requests_ : - pending_translation_requests_; - TranslationRequestMap::iterator iter = - translation_request_map.find(request->routing_id); - DCHECK(iter != translation_request_map.end()); - translation_request_map.erase(iter); - delete request; -} - -void TranslationService::SendResponseToRenderer( - const URLFetcher* const_url_fetcher, int error_code, - const TextChunksList& text_chunks_list) { - scoped_ptr<const URLFetcher> url_fetcher(const_url_fetcher); - RendererRequestInfoMap::iterator iter = - renderer_request_infos_.find(url_fetcher.get()); - DCHECK(iter != renderer_request_infos_.end()); - scoped_ptr<RendererRequestInfoList> request_info_list(iter->second); - DCHECK(error_code != 0 || - request_info_list->size() == text_chunks_list.size()); - for (size_t i = 0; i < request_info_list->size(); ++i) { - RendererRequestInfo& request_info = request_info_list->at(i); - message_sender_->Send( - new ViewMsg_TranslateTextReponse(request_info.routing_id, - request_info.work_id, - error_code, - error_code ? TextChunks() : - text_chunks_list[i])); - } - renderer_request_infos_.erase(iter); -} - -void TranslationService::TranslationFailed(const URLFetcher* url_fetcher) { - SendResponseToRenderer(url_fetcher, 1, TranslationService::TextChunksList()); -} - -string16 TranslationService::MergeTextChunks(const TextChunks& text_chunks) { - // If there is only 1 chunk, we don't need an anchor tag as there is no order - // to preserve. - if (text_chunks.size() == 1U) - return text_chunks[0]; - - string16 str; - for (size_t i = 0; i < text_chunks.size(); ++i) { - str.append(kCRAnchorTagStart); - str.append(IntToString16(i)); - str.append(kQuoteGreaterThan); - str.append(text_chunks[i]); - str.append(kClosingAnchorTag); - } - return str; -} - -bool TranslationService::FindOpenTagIndex(const string16& text, - size_t start_index, - size_t* tag_start_index, - size_t* tag_end_index, - int* id) { - DCHECK(tag_start_index && tag_end_index && id); - size_t text_length = text.length(); - if (start_index >= text_length) - return false; - - *tag_start_index = text.find(kCRAnchorTagStart, start_index); - if (*tag_start_index == std::string::npos) - return false; - - size_t quote_index = *tag_start_index + kCRAnchorTagStart.length(); - size_t close_quote_index = text.find(kQuote, quote_index); - if (close_quote_index == std::string::npos) { - NOTREACHED(); - return false; // Not a valid anchor tag. - } - - string16 id_str = text.substr(quote_index, close_quote_index - quote_index); - // Get the id. - if (!StringToInt(id_str, id)) { - NOTREACHED(); - return false; // Not a valid id, give up. - } - - *tag_end_index = text.find(kGreaterThan, close_quote_index); - if (*tag_end_index == std::string::npos || *tag_end_index >= text_length) - return false; - return true; -} - -void TranslationService::SplitIntoTextChunks(const string16& translated_text, - TextChunks* text_chunks) { - int id = -1; - size_t tag_start_index = 0; - size_t tag_end_index = 0; - if (!FindOpenTagIndex(translated_text, 0, &tag_start_index, &tag_end_index, - &id)) { - // No magic anchor tag, it was a single chunk. - text_chunks->push_back(translated_text); - return; - } - - // The server might send us some HTML with duplicated and unbalanced tags. - // We separate from one tag begining to the next, and merge tags with - // duplicate IDs. - std::set<int> parsed_tags; - string16 chunk; - while (tag_start_index != std::string::npos) { - int next_id = -1; - size_t previous_tag_end_index = tag_end_index; - if (!FindOpenTagIndex(translated_text, tag_end_index, - &tag_start_index, &tag_end_index, &next_id)) { - // Last tag. Just report as one chunk. - chunk = translated_text.substr(previous_tag_end_index + 1); - tag_start_index = std::string::npos; // So we break on next iteration. - } else { - // Extract the text for this tag. - DCHECK(tag_start_index > previous_tag_end_index); - chunk = - translated_text.substr(previous_tag_end_index + 1, - tag_start_index - previous_tag_end_index - 1); - } - chunk = RemoveTag(chunk); - // The translation server leaves some ampersand character in the - // translation. - chunk = UnescapeForHTML(chunk); - if (parsed_tags.count(id) > 0) { - // We have already seen this tag, add it to the previous text-chunk. - text_chunks->back().append(chunk); - } else { - text_chunks->push_back(chunk); - parsed_tags.insert(id); - } - id = next_id; - } -} - -string16 TranslationService::RemoveTag(const string16& text) { - // Remove any anchor tags, knowing they could be extra/unbalanced tags. - string16 result; - size_t start_index = text.find(kAnchorTagStart); - if (start_index == std::string::npos) { - result = text; - } else { - bool first_iter = true; - while (true) { - size_t stop_index = text.find(kGreaterThan, start_index); - size_t next_tag_index = text.find(kLessThan, start_index + 1); - // Ignore unclosed <a tag. (Ignore subsequent closing tags, they'll be - // removed in the next loop.) - if (stop_index == std::string::npos || - (next_tag_index != std::string::npos && - stop_index > next_tag_index)) { - result.append(text.substr(start_index)); - break; - } - if (start_index > 0 && first_iter) - result = text.substr(0, start_index); - start_index = text.find(kAnchorTagStart, start_index + 1); - if (start_index == std::string::npos) { - result += text.substr(stop_index + 1); - break; - } - result += text.substr(stop_index + 1, start_index - stop_index - 1); - first_iter = false; - } - } - - // Now remove </a> tags. - ReplaceSubstringsAfterOffset(&result, 0, kClosingAnchorTag, EmptyString16()); - return result; -} - -// static -void TranslationService::AddTextToRequestString(std::string* request, - const std::string& text, - const std::string& source_lang, - const std::string& target_lang, - bool secure) { - if (request->empty()) { - // First request, add required parameters. - request->append(kVersionParam); - request->append("=1.0&"); - request->append(kClientParam); - request->append("=cr&"); // cr = Chrome. - request->append(kFormatParam); - request->append("=html&"); - request->append(kLangPairParam); - request->append("="); - request->append(source_lang); - request->append("%7C"); // | URL encoded. - request->append(target_lang); - if (secure) { - request->append("&"); - request->append(kSSLParam); - request->append("=1"); - } - } - // IMPORTANT NOTE: if you make any change below, make sure to reflect them in - // text_param_length_ in TranslationService constructor. - request->append("&"); - request->append(kTextParam); - request->append("="); - request->append(text); -} diff --git a/chrome/browser/renderer_host/translation_service.h b/chrome/browser/renderer_host/translation_service.h deleted file mode 100644 index 666265b..0000000 --- a/chrome/browser/renderer_host/translation_service.h +++ /dev/null @@ -1,195 +0,0 @@ -// Copyright (c) 2010 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. - -#ifndef CHROME_BROWSER_RENDERER_HOST_TRANSLATION_SERVICE_H_ -#define CHROME_BROWSER_RENDERER_HOST_TRANSLATION_SERVICE_H_ - -#include <map> -#include <set> -#include <string> -#include <vector> - -#include "base/lazy_instance.h" -#include "base/scoped_ptr.h" -#include "base/string16.h" -#include "chrome/browser/net/url_fetcher.h" -#include "ipc/ipc_message.h" -#include "testing/gtest/include/gtest/gtest_prod.h" - -class DictionaryValue; -class TranslationServiceTest; -class TranslateURLFetcherDelegate; -class URLFetcher; - -// The TranslationService class is used to translate text. -// There is one TranslationService per renderer process. -// It receives requests to translate text from the different render views of the -// render process, provided in lists (text chunks), where the words should be -// translated without changing the chunks order. -// It groups multiple such requests and sends them for translation to the Google -// translation server. When it receives the response, it dispatches it to the -// appropriate render view. - -class TranslationService : public URLFetcher::Delegate { - public: - explicit TranslationService(IPC::Message::Sender* message_sender); - virtual ~TranslationService(); - - // Sends the specified text for translation, from |source_language| to - // |target_language|. If |secure| is true, a secure connection is used when - // sending the text to the external translation server. - // When the translation results have been received, it sends a - // ViewMsg_TranslateTextReponse message on the renderer at |routing_id|. - void Translate(int routing_id, - int page_id, - int work_id, - const std::vector<string16>& text_chunks, - const std::string& source_language, - const std::string& target_language, - bool secure); - - // Sends the pending translation request for the specified renderer to the - // translation server. - void SendTranslationRequestForRenderer(int renderer_id, bool secure); - - // URLFetcher::Delegate implementation. - virtual void OnURLFetchComplete(const URLFetcher* source, - const GURL& url, - const URLRequestStatus& status, - int response_code, - const ResponseCookies& cookies, - const std::string& data); - - // Returns true if the TranslationService is enabled. - static bool IsTranslationEnabled(); - - // Fills |languages| with the list of languages that the translate server can - // translate to and from. - static void GetSupportedLanguages(std::vector<std::string>* languages); - - // Returns the language code that can be used with the Translate method for a - // specified |chrome_locale|. - static std::string GetLanguageCode(const std::string& chrome_locale); - - // Returns true if |page_language| is supported by the translation server. - static bool IsSupportedLanguage(const std::string& page_language); - - protected: - // The amount of time in ms after which a pending request is sent if no other - // translation request has been received. - // Overriden in tests. - virtual int GetSendRequestDelay() const; - - private: - friend class TranslationServiceTest; - friend class TranslateURLFetcherDelegate; - FRIEND_TEST(TranslationServiceTest, MergeTestChunks); - FRIEND_TEST(TranslationServiceTest, SplitIntoTextChunks); - FRIEND_TEST(TranslationServiceTest, RemoveTag); - - struct TranslationRequest; - - // The information necessary to return the translated text to the renderer. - struct RendererRequestInfo { - RendererRequestInfo() : routing_id(0), work_id(0) {} - RendererRequestInfo(int routing_id, int work_id) - : routing_id(routing_id), - work_id(work_id) { - } - int routing_id; - int work_id; - }; - - typedef std::vector<RendererRequestInfo> RendererRequestInfoList; - - typedef std::vector<string16> TextChunks; - typedef std::vector<TextChunks> TextChunksList; - // Maps from a RenderView routing id to the pending request for the - // translation server. - typedef std::map<int, TranslationRequest*> TranslationRequestMap; - - typedef std::map<const URLFetcher*, RendererRequestInfoList*> - RendererRequestInfoMap; - - // Sends the passed request to the translations server. - // Warning: the request is deleted when this call returns. - void SendRequestToTranslationServer(TranslationRequest* request); - - // Called by the URLFetcherDelegate when the translation associated with - // |url_fetcher| has been performed. Sends the appropriate message back to - // the renderer and deletes the URLFetcher. - void SendResponseToRenderer(const URLFetcher* url_fetcher, - int error_code, - const TextChunksList& text_chunks_list); - - // Notifies the renderer that we failed to translate the request associated - // with |url_fetcher|. - void TranslationFailed(const URLFetcher* source); - - // Merges all text chunks to be translated into a single string that can be - // sent to the translate server, surrounding each chunk with an anchor tag - // to preserve chunk order in the translated version. - string16 MergeTextChunks(const TextChunks& text_chunks); - - // Splits the translated text into its original text chunks, removing the - // anchor tags wrapper that were added to preserve order. - void SplitIntoTextChunks(const string16& translated_text, - TextChunks* text_chunks); - - // Removes the HTML anchor tag surrounding |text| and returns the resulting - // string. - string16 RemoveTag(const string16& text); - - // Find the next anchor tag in |text| starting at |start_index|. - // Sets |id| (which must be non NULL) to the id property of the tag (which is - // expected to be an int). Sets |tag_start_index| and |tag_end_index| to the - // index of the beginning/end of the next tag. - // Returns true if a tag was found and it is not at the end of the string, - // false otherwise in which case |id|, |tag_start_index| and |tag_end_index| - // are not set. - bool FindOpenTagIndex(const string16& text, - size_t start_index, - size_t* tag_start_index, - size_t* tag_end_index, - int* id); - - // Adds |text| to the string request in/out param |request|. If |request| is - // empty, then the source, target language as well as the secure parameters - // are also added. - static void AddTextToRequestString(std::string* request, - const std::string& text, - const std::string& source_language, - const std::string& target_language, - bool secure); - - // The channel used to communicate with the renderer. - IPC::Message::Sender* message_sender_; - - // Map used to retrieve the context of requests when the URLFetcher notifies - // that it got a response. - RendererRequestInfoMap renderer_request_infos_; - - TranslationRequestMap pending_translation_requests_; - TranslationRequestMap pending_secure_translation_requests_; - - // Strings used for parsing. - const string16 kCRAnchorTagStart; - const string16 kAnchorTagStart; - const string16 kClosingAnchorTag; - const string16 kQuote; - const string16 kGreaterThan; - const string16 kLessThan; - const string16 kQuoteGreaterThan; - - // The size taken by the parameters and separators needed when adding text to - // a request string. - static size_t text_param_length_; - - // The language supported by the translation server. - static base::LazyInstance<std::set<std::string> > supported_languages_; - - DISALLOW_COPY_AND_ASSIGN(TranslationService); -}; - -#endif // CHROME_BROWSER_RENDERER_HOST_TRANSLATION_SERVICE_H_ diff --git a/chrome/browser/renderer_host/translation_service_unittest.cc b/chrome/browser/renderer_host/translation_service_unittest.cc deleted file mode 100644 index f973502..0000000 --- a/chrome/browser/renderer_host/translation_service_unittest.cc +++ /dev/null @@ -1,505 +0,0 @@ -// Copyright (c) 2010 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 "base/json/json_writer.h" -#include "base/stl_util-inl.h" -#include "base/string_tokenizer.h" -#include "base/string_util.h" -#include "chrome/browser/net/test_url_fetcher_factory.h" -#include "chrome/browser/renderer_host/translation_service.h" -#include "chrome/common/render_messages.h" -#include "ipc/ipc_message.h" -#include "net/base/escape.h" -#include "net/url_request/url_request_status.h" -#include "testing/gtest/include/gtest/gtest.h" - -typedef std::vector<string16> TextChunks; -typedef std::vector<TextChunks> TextChunksList; - -class TestMessageSender : public IPC::Message::Sender { - public: - virtual ~TestMessageSender() { - ClearMessages(); - } - - virtual bool Send(IPC::Message* msg) { - messages_.push_back(msg); - return true; - } - - size_t message_count() const { return messages_.size(); } - - void GetMessageParameters(size_t message_index, int* routing_id, - int* work_id, int* error_id, - std::vector<string16>* text_chunks) { - ASSERT_LT(message_index, messages_.size()); - *routing_id = messages_.at(message_index)->routing_id(); - ViewMsg_TranslateTextReponse::Read(messages_.at(message_index), - work_id, error_id, text_chunks); - } - - void ClearMessages() { - STLDeleteElements(&messages_); - messages_.clear(); - } - - std::vector<IPC::Message*> messages_; - MessageLoop message_loop_; -}; - -class TestTranslationService : public TranslationService { - public: - explicit TestTranslationService(IPC::Message::Sender* message_sender) - : TranslationService(message_sender) {} - - virtual int GetSendRequestDelay() const { - return 0; - } -}; - -class TranslationServiceTest : public testing::Test { - public: - TranslationServiceTest() : translation_service_(&message_sender_) {} - - virtual void SetUp() { - URLFetcher::set_factory(&url_fetcher_factory_); - } - - virtual void TearDown() { - URLFetcher::set_factory(NULL); - } - - protected: - TestURLFetcherFactory url_fetcher_factory_; - TestMessageSender message_sender_; - TestTranslationService translation_service_; -}; - -static void SimulateErrorResponse(TestURLFetcher* url_fetcher, - int response_code) { - url_fetcher->delegate()->OnURLFetchComplete(url_fetcher, - url_fetcher->original_url(), - URLRequestStatus(), - response_code, - ResponseCookies(), - std::string()); -} - -// Merges the strings in |text_chunks| into the string that the translation -// server received. -static string16 BuildResponseString(const TextChunks& text_chunks) { - string16 text; - for (size_t i = 0; i < text_chunks.size(); ++i) { - text.append(ASCIIToUTF16("<a _CR_TR_ id='")); - text.append(IntToString16(i)); - text.append(ASCIIToUTF16("'>")); - text.append(text_chunks.at(i)); - text.append(ASCIIToUTF16("</a>")); - } - return text; -} - -static void SimulateSuccessfulResponse(TestURLFetcher* url_fetcher, - const TextChunksList& text_chunks_list) { - scoped_ptr<ListValue> list(new ListValue()); - for (TextChunksList::const_iterator iter = text_chunks_list.begin(); - iter != text_chunks_list.end(); ++iter) { - list->Append(Value::CreateStringValueFromUTF16(BuildResponseString(*iter))); - } - - std::string response; - base::JSONWriter::Write(list.get(), false, &response); - url_fetcher->delegate()->OnURLFetchComplete(url_fetcher, - url_fetcher->original_url(), - URLRequestStatus(), - 200, - ResponseCookies(), - response); -} - -static void SimulateSimpleSuccessfulResponse(TestURLFetcher* url_fetcher, - const char* const* c_text_chunks, - size_t text_chunks_length) { - TextChunks text_chunks; - for (size_t i = 0; i < text_chunks_length; i++) - text_chunks.push_back(ASCIIToUTF16(c_text_chunks[i])); - - string16 text = BuildResponseString(text_chunks); - - std::string response; - scoped_ptr<Value> json_text(Value::CreateStringValueFromUTF16(text)); - base::JSONWriter::Write(json_text.get(), false, &response); - url_fetcher->delegate()->OnURLFetchComplete(url_fetcher, - url_fetcher->original_url(), - URLRequestStatus(), - 200, - ResponseCookies(), - response); -} - -// Parses the upload data from |url_fetcher| and puts the value for the q -// parameters in |text_chunks|. -static void ExtractQueryStringsFromUploadData(TestURLFetcher* url_fetcher, - TextChunks* text_chunks) { - std::string upload_data = url_fetcher->upload_data(); - - CStringTokenizer str_tok(upload_data.c_str(), upload_data.c_str() + - upload_data.length(), "&"); - while (str_tok.GetNext()) { - std::string tok = str_tok.token(); - if (tok.size() > 1U && tok.at(0) == 'q' && tok.at(1) == '=') - text_chunks->push_back(UnescapeForHTML(ASCIIToUTF16(tok.substr(2)))); - } -} - -TEST_F(TranslationServiceTest, MergeTestChunks) { - TranslationService translation_service(NULL); - std::vector<string16> input; - input.push_back(ASCIIToUTF16("Hello")); - string16 result = translation_service.MergeTextChunks(input); - EXPECT_EQ(ASCIIToUTF16("Hello"), result); - input.push_back(ASCIIToUTF16(" my name")); - input.push_back(ASCIIToUTF16(" is")); - input.push_back(ASCIIToUTF16(" Jay.")); - result = translation_service.MergeTextChunks(input); - EXPECT_EQ(ASCIIToUTF16("<a _CR_TR_ id='0'>Hello</a>" - "<a _CR_TR_ id='1'> my name</a>" - "<a _CR_TR_ id='2'> is</a>" - "<a _CR_TR_ id='3'> Jay.</a>"), - result); -} - -TEST_F(TranslationServiceTest, RemoveTag) { - const char* kInputs[] = { - "", "Hello", "<a ></a>", " <a href='http://www.google.com'> Link </a>", - "<a >Link", "<a link</a>", "<a id=1><a id=1>broken</a></a>", - "<a id=1>broken</a></a> bad bad</a>", - }; - const char* kExpected[] = { - "", "Hello", "", " Link ", "Link", "<a link", "broken", "broken bad bad" - }; - - TranslationService translation_service(NULL); - ASSERT_EQ(arraysize(kInputs), arraysize(kExpected)); - for (size_t i = 0; i < arraysize(kInputs); ++i) { - SCOPED_TRACE(::testing::Message::Message() << "Iteration " << i); - string16 input = ASCIIToUTF16(kInputs[i]); - string16 output = translation_service.RemoveTag(input); - EXPECT_EQ(ASCIIToUTF16(kExpected[i]), output); - } -} - -// Tests that we deal correctly with the various results the translation server -// can return, including the buggy ones. -TEST_F(TranslationServiceTest, SplitIntoTextChunks) { - TranslationService translation_service(NULL); - - // Simple case. - std::vector<string16> text_chunks; - translation_service.SplitIntoTextChunks(ASCIIToUTF16("Hello"), &text_chunks); - ASSERT_EQ(1U, text_chunks.size()); - EXPECT_EQ(ASCIIToUTF16("Hello"), text_chunks[0]); - - text_chunks.clear(); - - // Multiple chunks case, correct syntax. - translation_service.SplitIntoTextChunks( - ASCIIToUTF16("<a _CR_TR_ id='0'>Bonjour</a>" - "<a _CR_TR_ id='1'> mon nom</a>" - "<a _CR_TR_ id='2'> est</a>" - "<a _CR_TR_ id='3'> Jay.</a>"), &text_chunks); - ASSERT_EQ(4U, text_chunks.size()); - EXPECT_EQ(ASCIIToUTF16("Bonjour"), text_chunks[0]); - EXPECT_EQ(ASCIIToUTF16(" mon nom"), text_chunks[1]); - EXPECT_EQ(ASCIIToUTF16(" est"), text_chunks[2]); - EXPECT_EQ(ASCIIToUTF16(" Jay."), text_chunks[3]); - text_chunks.clear(); - - // Multiple chunks case, duplicate and unbalanced tags. - // For info, original input: - // <a _CR_TRANSLATE_ id='0'> Experience </a><a _CR_TRANSLATE_ id='1'>Nexus One - // </a><a _CR_TRANSLATE_ id='2'>, the new Android phone from Google</a> - translation_service.SplitIntoTextChunks( - ASCIIToUTF16("<a _CR_TR_ id='0'>Experience</a> <a _CR_TR_ id='1'>Nexus" - "<a _CR_TR_ id='2'> One,</a></a> <a _CR_TR_ id='2'>the new " - "Android Phone</a>"), &text_chunks); - ASSERT_EQ(3U, text_chunks.size()); - EXPECT_EQ(ASCIIToUTF16("Experience "), text_chunks[0]); - EXPECT_EQ(ASCIIToUTF16("Nexus"), text_chunks[1]); - EXPECT_EQ(ASCIIToUTF16(" One, the new Android Phone"), text_chunks[2]); - text_chunks.clear(); - - // Other incorrect case: - // Original input: - // <a _CR_TR_ id='0'>Benzinpreis-</a><a _CR_TR_ id='1'>vergleich</a> - translation_service.SplitIntoTextChunks( - ASCIIToUTF16("<a _CR_TR_ id='0'>Gasoline <a _CR_TR_ id='1'>" - "price-comparison</a></a>"), &text_chunks); - ASSERT_EQ(2U, text_chunks.size()); - EXPECT_EQ(ASCIIToUTF16("Gasoline "), text_chunks[0]); - EXPECT_EQ(ASCIIToUTF16("price-comparison"), text_chunks[1]); - text_chunks.clear(); - - // Other incorrect case: - // Original input: - // <a _CR_TR_ id='0'>Bußgeld-</a><a _CR_TR_ id='1'>rechner</a> - translation_service.SplitIntoTextChunks( - ASCIIToUTF16("<a _CR_TR_ id='1'><a _CR_TR_ id='0'>Fine-computer</a>" - "</a>"), &text_chunks); - ASSERT_EQ(2U, text_chunks.size()); - EXPECT_EQ(ASCIIToUTF16(""), text_chunks[0]); - EXPECT_EQ(ASCIIToUTF16("Fine-computer"), text_chunks[1]); - text_chunks.clear(); - - translation_service.SplitIntoTextChunks( - ASCIIToUTF16("<a _CR_TR_ id='0'>The mountain live .</a> " - "<a _CR_TR_ id='1'>By Philipp Wittrock</a> <a _CR_TR_ id='0'>are</a> " - "<a _CR_TR_ id='2'>more ...</a> <a _CR_TR_ id='3'>Video</a> " - "<a _CR_TR_ id='4'>Forum</a>"), &text_chunks); - ASSERT_EQ(5U, text_chunks.size()); - EXPECT_EQ(ASCIIToUTF16("The mountain live . "), text_chunks[0]); - EXPECT_EQ(ASCIIToUTF16("By Philipp Wittrock are "), text_chunks[1]); - EXPECT_EQ(ASCIIToUTF16("more ... "), text_chunks[2]); - EXPECT_EQ(ASCIIToUTF16("Video "), text_chunks[3]); - EXPECT_EQ(ASCIIToUTF16("Forum"), text_chunks[4]); - text_chunks.clear(); - - // Make sure we support ending with a start tag. - translation_service.SplitIntoTextChunks( - ASCIIToUTF16("<a _CR_TR_ id='0'>Hello</a><a _CR_TR_ id='1'>"), - &text_chunks); - ASSERT_EQ(2U, text_chunks.size()); - EXPECT_EQ(ASCIIToUTF16("Hello"), text_chunks[0]); - EXPECT_EQ(EmptyString16(), text_chunks[1]); -} - -// Tests that a successful translate works as expected. -TEST_F(TranslationServiceTest, SimpleSuccessfulTranslation) { - const char* const kEnglishTextChunks[] = { - "An atom is talking to another atom:", - "- I think I lost an electron.", - "- Are you sure?", - "- I am positive." - }; - - const char* const kFrenchTextChunks[] = { - "Un atome parle a un autre atome:", - "- Je crois que j'ai perdu un electron.", - "- T'es sur?", - "- Je suis positif." // Note that the joke translates poorly in French. - }; - - // Translate some text unsecurely. - std::vector<string16> text_chunks; - for (size_t i = 0; i < arraysize(kEnglishTextChunks); ++i) - text_chunks.push_back(ASCIIToUTF16(kEnglishTextChunks[i])); - translation_service_.Translate(0, 0, 0, text_chunks, "en", "fr", false); - - TestURLFetcher* url_fetcher = url_fetcher_factory_.GetFetcherByID(0); - // The message was too short to triger the sending of a request to the - // translation request. A task has been pushed for that. - EXPECT_TRUE(url_fetcher == NULL); - MessageLoop::current()->RunAllPending(); - - // Now the task has been run, the message should have been sent. - url_fetcher = url_fetcher_factory_.GetFetcherByID(0); - ASSERT_TRUE(url_fetcher != NULL); - // Check the URL is HTTP. - EXPECT_FALSE(url_fetcher->original_url().SchemeIsSecure()); - - // Let's simulate the JSON response from the server. - SimulateSimpleSuccessfulResponse(url_fetcher, &(kFrenchTextChunks[0]), - arraysize(kFrenchTextChunks)); - - // This should have triggered a ViewMsg_TranslateTextReponse message. - ASSERT_EQ(1U, message_sender_.message_count()); - - // Test the message has the right translation. - int routing_id = 0; - int work_id = 0; - int error_id = 0; - std::vector<string16> translated_text_chunks; - message_sender_.GetMessageParameters(0, &routing_id, &work_id, &error_id, - &translated_text_chunks); - EXPECT_EQ(0, routing_id); - EXPECT_EQ(0, error_id); - ASSERT_EQ(arraysize(kFrenchTextChunks), translated_text_chunks.size()); - for (size_t i = 0; i < arraysize(kFrenchTextChunks); ++i) - EXPECT_EQ(ASCIIToUTF16(kFrenchTextChunks[i]), translated_text_chunks[i]); -} - -// Tests that on failure we send the expected error message. -TEST_F(TranslationServiceTest, FailedTranslation) { - std::vector<string16> text_chunks; - text_chunks.push_back(ASCIIToUTF16("Hello")); - translation_service_.Translate(0, 0, 0, text_chunks, "en", "fr", false); - - // Run the task that creates the URLFetcher and sends the request. - MessageLoop::current()->RunAllPending(); - - TestURLFetcher* url_fetcher = url_fetcher_factory_.GetFetcherByID(0); - ASSERT_TRUE(url_fetcher != NULL); - SimulateErrorResponse(url_fetcher, 500); - - // This should have triggered a ViewMsg_TranslateTextReponse message. - ASSERT_EQ(1U, message_sender_.message_count()); - - // Test the message has some error. - int routing_id = 0; - int work_id = 0; - int error_id = 0; - std::vector<string16> translated_text_chunks; - message_sender_.GetMessageParameters(0, &routing_id, &work_id, &error_id, - &translated_text_chunks); - - EXPECT_NE(0, error_id); // Anything but 0 means there was an error. - EXPECT_TRUE(translated_text_chunks.empty()); -} - -// Tests that a secure translation is done over a secure connection. -TEST_F(TranslationServiceTest, SecureTranslation) { - std::vector<string16> text_chunks; - text_chunks.push_back(ASCIIToUTF16("Hello")); - translation_service_.Translate(0, 0, 0, text_chunks, "en", "fr", - true /* secure */); - - // Run the task that creates the URLFetcher and sends the request. - MessageLoop::current()->RunAllPending(); - - TestURLFetcher* url_fetcher = url_fetcher_factory_.GetFetcherByID(0); - ASSERT_TRUE(url_fetcher != NULL); - EXPECT_TRUE(url_fetcher->original_url().SchemeIsSecure()); -} - -// Test mixing requests from different renderers. -TEST_F(TranslationServiceTest, MultipleRVRequests) { - const char* const kExpectedRV1_1 = "Bonjour RV1"; - const char* const kExpectedRV1_2 = "Encore bonjour RV1"; - const char* const kExpectedRV1_3 = "Encore bonjour a nouveau RV1"; - const char* const kExpectedRV2 = "Bonjour RV2"; - const char* const kExpectedRV3 = "Bonjour RV3"; - - TextChunks text_chunks; - text_chunks.push_back(ASCIIToUTF16("Hello RV1")); - text_chunks.push_back(ASCIIToUTF16("Hello again RV1")); - translation_service_.Translate(1, 0, 0, text_chunks, "en", "fr", false); - text_chunks.clear(); - text_chunks.push_back(ASCIIToUTF16("Hello RV2")); - translation_service_.Translate(2, 0, 0, text_chunks, "en", "fr", false); - text_chunks.clear(); - text_chunks.push_back(ASCIIToUTF16("Hello again one more time RV1")); - translation_service_.Translate(1, 0, 1, text_chunks, "en", "fr", false); - text_chunks.clear(); - text_chunks.push_back(ASCIIToUTF16("Hello RV3")); - translation_service_.Translate(3, 0, 0, text_chunks, "en", "fr", false); - - // Run the tasks that create the URLFetcher and send the requests. - MessageLoop::current()->RunAllPending(); - - // We should have 3 pending URL fetchers. (The 2 translate requests for RV1 - // should have been grouped in 1.) Simluate the translation server responses. - TestURLFetcher* url_fetcher = url_fetcher_factory_.GetFetcherByID(1); - ASSERT_TRUE(url_fetcher != NULL); - - TextChunksList text_chunks_list; - text_chunks.clear(); - text_chunks.push_back(ASCIIToUTF16(kExpectedRV1_1)); - text_chunks.push_back(ASCIIToUTF16(kExpectedRV1_2)); - text_chunks_list.push_back(text_chunks); - text_chunks.clear(); - text_chunks.push_back(ASCIIToUTF16(kExpectedRV1_3)); - text_chunks_list.push_back(text_chunks); - SimulateSuccessfulResponse(url_fetcher, text_chunks_list); - - url_fetcher = url_fetcher_factory_.GetFetcherByID(2); - ASSERT_TRUE(url_fetcher != NULL); - SimulateSimpleSuccessfulResponse(url_fetcher, &kExpectedRV2, 1); - - url_fetcher = url_fetcher_factory_.GetFetcherByID(3); - ASSERT_TRUE(url_fetcher != NULL); - SimulateSimpleSuccessfulResponse(url_fetcher, &kExpectedRV3, 1); - - // This should have triggered 4 ViewMsg_TranslateTextReponse messages. - ASSERT_EQ(4U, message_sender_.message_count()); - - const int kExpectedRoutingID[] = { 1, 1, 2, 3 }; - const int kExpectedWorkID[] = { 0, 1, 0, 0 }; - const size_t kExpectedStringCount[] = { 2U, 1U, 1U, 1U }; - - // Test the messages have the expected content. - for (size_t i = 0; i < 4; i++) { - SCOPED_TRACE(::testing::Message::Message() << "Iteration " << i); - int routing_id = 0; - int work_id = 0; - int error_id = 0; - std::vector<string16> translated_text_chunks; - message_sender_.GetMessageParameters(i, &routing_id, &work_id, &error_id, - &translated_text_chunks); - EXPECT_EQ(kExpectedRoutingID[i], routing_id); - EXPECT_EQ(kExpectedWorkID[i], work_id); - EXPECT_EQ(0, error_id); - EXPECT_EQ(kExpectedStringCount[i], translated_text_chunks.size()); - // TODO(jcampan): we should compare the strings. - } -} - -// Tests sending more than the max size. -TEST_F(TranslationServiceTest, MoreThanMaxSizeRequests) { - std::string one_kb_string(1024U, 'A'); - TextChunks text_chunks; - text_chunks.push_back(ASCIIToUTF16(one_kb_string)); - // Send 2 small requests, then a big one. - translation_service_.Translate(1, 0, 0, text_chunks, "en", "fr", false); - translation_service_.Translate(1, 0, 1, text_chunks, "en", "fr", false); - // We need a string big enough to be more than 30KB on top of the other 2 - // requests, but to be less than 30KB when sent (that sizes includes the - // other parameters required by the translation server). - std::string twenty_nine_kb_string(29 * 1024, 'G'); - text_chunks.clear(); - text_chunks.push_back(ASCIIToUTF16(twenty_nine_kb_string)); - translation_service_.Translate(1, 0, 2, text_chunks, "en", "fr", false); - - // Without any task been run, the 2 first requests should have been sent. - TestURLFetcher* url_fetcher = url_fetcher_factory_.GetFetcherByID(1); - TextChunks query_strings; - ExtractQueryStringsFromUploadData(url_fetcher, &query_strings); - ASSERT_EQ(2U, query_strings.size()); - EXPECT_EQ(one_kb_string, UTF16ToASCII(query_strings[0])); - EXPECT_EQ(one_kb_string, UTF16ToASCII(query_strings[1])); - - // Then when the task runs, the big request is sent. - MessageLoop::current()->RunAllPending(); - - url_fetcher = url_fetcher_factory_.GetFetcherByID(1); - query_strings.clear(); - ExtractQueryStringsFromUploadData(url_fetcher, &query_strings); - ASSERT_EQ(1U, query_strings.size()); - EXPECT_EQ(twenty_nine_kb_string, UTF16ToASCII(query_strings[0])); -} - -// Test mixing secure/insecure requests and that secure requests are always sent -// over HTTPS. -TEST_F(TranslationServiceTest, MixedHTTPAndHTTPS) { - const char* kUnsecureMessage = "Hello"; - const char* kSecureMessage = "Hello_Secure"; - - std::vector<string16> text_chunks; - text_chunks.push_back(ASCIIToUTF16(kUnsecureMessage)); - translation_service_.Translate(0, 0, 0, text_chunks, "en", "fr", false); - text_chunks.clear(); - text_chunks.push_back(ASCIIToUTF16(kSecureMessage)); - translation_service_.Translate(0, 0, 0, text_chunks, "en", "fr", true); - - // Run the task that creates the URLFetcher and send the request. - MessageLoop::current()->RunAllPending(); - - // We only get the last URLFetcher since we id them based on their routing id - // which we want to be the same in that test. We'll just check that as - // expected the last one is the HTTPS and contains only the secure string. - TestURLFetcher* url_fetcher = url_fetcher_factory_.GetFetcherByID(0); - TextChunks query_strings; - ExtractQueryStringsFromUploadData(url_fetcher, &query_strings); - ASSERT_EQ(1U, query_strings.size()); - EXPECT_EQ(kSecureMessage, UTF16ToASCII(query_strings[0])); -} diff --git a/chrome/browser/resources/translate.js b/chrome/browser/resources/translate.js new file mode 100644 index 0000000..d761c11 --- /dev/null +++ b/chrome/browser/resources/translate.js @@ -0,0 +1,111 @@ +// Copyright (c) 2010 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. + +// This code is used in conjunction with the Google Translate Element script. +// It is injected in a page to translate it from one language to another. +// It should be included in the page before the Translate Element script. + +var cr = {}; + +cr.googleTranslate = (function() { + // Internal states. + var lib; + var libReady = false; + var error = false; + var finished = false; + var checkReadyCount = 0; + + function checkLibReady() { + if (lib.isAvailable()) { + libReady = true; + return; + } + if (checkReadyCount++ > 5) { + error = true; + return; + } + setTimeout(checkLibReady, 100); + } + + function onTranslateProgress(progress, opt_finished, opt_error) { + finished = opt_finished; + // opt_error can be 'undefined'. + if (typeof opt_error == 'boolean' && opt_error) { + error = true; + // We failed to translate, restore so the page is in a consistent state. + lib.restore(); + } + } + + // Public API. + return { + /** + * Whether the library is ready. + * The translate function should only be called when |libReady| is true. + * @type {boolean} + */ + get libReady() { + return libReady; + }, + + /** + * Whether the current translate has finished successfully. + * @type {boolean} + */ + get finished() { + return finished; + }, + + /** + * Whether an error occured initializing the library of translating the + * page. + * @type {boolean} + */ + get error() { + return error; + }, + + /** + * Translate the page contents. Note that the translation is asynchronous. + * You need to regularly check the state of |finished| and |error| to know + * if the translation finished or if there was an error. + * @param {string} originalLang The language the page is in. + * @param {string} targetLang The language the page should be translated to. + * @return {boolean} False if the translate library was not ready, in which + * case the translation is not started. True otherwise. + */ + translate: function(originalLang, targetLang) { + finished = false; + error = false; + if (!libReady) + return false; + lib.translatePage(originalLang, targetLang, onTranslateProgress); + return true; + }, + + /** + * Reverts the page contents to its original value, effectively reverting + * any performed translation. Does nothing if the page was not translated. + */ + revert: function() { + lib.restore(); + }, + + /** + * Entry point called by the Translate Element once it has been injected in + * the page. + */ + onTranslateElementLoad : function() { + try { + lib = google.translate.TranslateService({}); + } catch(err) { + error = true; + return; + } + // The TranslateService is not available immediately as it needs to start + // Flash. Let's wait until it is ready. + checkLibReady(); + } + }; +})(); diff --git a/chrome/browser/tab_contents/render_view_context_menu.cc b/chrome/browser/tab_contents/render_view_context_menu.cc index da20bec..9a4f951 100644 --- a/chrome/browser/tab_contents/render_view_context_menu.cc +++ b/chrome/browser/tab_contents/render_view_context_menu.cc @@ -26,7 +26,6 @@ #include "chrome/browser/pref_service.h" #include "chrome/browser/profile.h" #include "chrome/browser/renderer_host/render_view_host.h" -#include "chrome/browser/renderer_host/translation_service.h" #include "chrome/browser/search_engines/template_url_model.h" #include "chrome/browser/spellcheck_host.h" #include "chrome/browser/spellchecker_platform_engine.h" @@ -407,15 +406,11 @@ void RenderViewContextMenu::AppendPageItems() { AppendSeparator(); AppendMenuItem(IDS_CONTENT_CONTEXT_SAVEPAGEAS); AppendMenuItem(IDS_CONTENT_CONTEXT_PRINT); - if (TranslationService::IsTranslationEnabled() || - TranslateManager::test_enabled()) { - std::string locale = g_browser_process->GetApplicationLocale(); - locale = TranslationService::GetLanguageCode(locale); - string16 language = - l10n_util::GetDisplayNameForLocale(locale, locale, true); - AppendMenuItem(IDS_CONTENT_CONTEXT_TRANSLATE, - l10n_util::GetStringFUTF16(IDS_CONTENT_CONTEXT_TRANSLATE, language)); - } + std::string locale = g_browser_process->GetApplicationLocale(); + locale = TranslateManager::GetLanguageCode(locale); + string16 language = l10n_util::GetDisplayNameForLocale(locale, locale, true); + AppendMenuItem(IDS_CONTENT_CONTEXT_TRANSLATE, + l10n_util::GetStringFUTF16(IDS_CONTENT_CONTEXT_TRANSLATE, language)); AppendMenuItem(IDS_CONTENT_CONTEXT_VIEWPAGESOURCE); AppendMenuItem(IDS_CONTENT_CONTEXT_VIEWPAGEINFO); } @@ -1052,13 +1047,14 @@ void RenderViewContextMenu::ExecuteItemCommand(int id) { std::string original_lang = source_tab_contents_->language_state().original_language(); std::string target_lang = g_browser_process->GetApplicationLocale(); - target_lang = TranslationService::GetLanguageCode(target_lang); + target_lang = TranslateManager::GetLanguageCode(target_lang); // Since the user decided to translate for that language and site, clears // any preferences for not translating them. TranslatePrefs prefs(profile_->GetPrefs()); prefs.RemoveLanguageFromBlacklist(original_lang); prefs.RemoveSiteFromBlacklist(params_.page_url.HostNoBrackets()); - source_tab_contents_->TranslatePage(original_lang, target_lang); + Singleton<TranslateManager>::get()->TranslatePage( + source_tab_contents_, original_lang, target_lang); break; } diff --git a/chrome/browser/tab_contents/tab_contents.cc b/chrome/browser/tab_contents/tab_contents.cc index 8f49bb1..683d63a 100644 --- a/chrome/browser/tab_contents/tab_contents.cc +++ b/chrome/browser/tab_contents/tab_contents.cc @@ -50,7 +50,6 @@ #include "chrome/browser/renderer_host/render_widget_host_view.h" #include "chrome/browser/renderer_host/resource_request_details.h" #include "chrome/browser/renderer_host/site_instance.h" -#include "chrome/browser/renderer_host/translation_service.h" #include "chrome/browser/renderer_host/web_cache_manager.h" #include "chrome/browser/renderer_preferences_util.h" #include "chrome/browser/search_engines/template_url_fetcher.h" @@ -833,22 +832,6 @@ void TabContents::ShowPageInfo(const GURL& url, delegate_->ShowPageInfo(profile(), url, ssl, show_history); } -void TabContents::TranslatePage(const std::string& source_lang, - const std::string& target_lang) { - NavigationEntry* entry = controller_.GetActiveEntry(); - if (!entry) { - NOTREACHED(); - return; - } - language_state_.set_translation_pending(true); - render_view_host()->TranslatePage(entry->page_id(), source_lang, target_lang); -} - -void TabContents::RevertTranslatedPage() { - // TODO(jcampan): revert translated page to original and remove translate - // infobar. -} - ConstrainedWindow* TabContents::CreateConstrainedDialog( ConstrainedWindowDelegate* delegate) { ConstrainedWindow* window = diff --git a/chrome/browser/tab_contents/tab_contents.h b/chrome/browser/tab_contents/tab_contents.h index b9e0f15..123157d 100644 --- a/chrome/browser/tab_contents/tab_contents.h +++ b/chrome/browser/tab_contents/tab_contents.h @@ -376,13 +376,6 @@ class TabContents : public PageNavigator, const NavigationEntry::SSLStatus& ssl, bool show_history); - // Translates the page contents from |source_lang| to |target_lang|. - void TranslatePage(const std::string& source_lang, - const std::string& target_lang); - - // Reverts a translated page to original page. - void RevertTranslatedPage(); - // Window management --------------------------------------------------------- // Create a new window constrained to this TabContents' clip and visibility. diff --git a/chrome/browser/translate/translate_infobars_delegates.cc b/chrome/browser/translate/translate_infobars_delegates.cc index 5caff33..0f66357 100644 --- a/chrome/browser/translate/translate_infobars_delegates.cc +++ b/chrome/browser/translate/translate_infobars_delegates.cc @@ -7,8 +7,8 @@ #include "app/l10n_util.h" #include "app/resource_bundle.h" #include "chrome/browser/browser_process.h" -#include "chrome/browser/renderer_host/translation_service.h" #include "chrome/browser/tab_contents/tab_contents.h" +#include "chrome/browser/translate/translate_manager.h" #include "grit/generated_resources.h" #include "grit/theme_resources.h" @@ -68,12 +68,12 @@ void TranslateInfoBarDelegate::ModifyTargetLanguage(int lang_index) { void TranslateInfoBarDelegate::GetAvailableOriginalLanguages( std::vector<std::string>* languages) { - TranslationService::GetSupportedLanguages(languages); + TranslateManager::GetSupportedLanguages(languages); } void TranslateInfoBarDelegate::GetAvailableTargetLanguages( std::vector<std::string>* languages) { - TranslationService::GetSupportedLanguages(languages); + TranslateManager::GetSupportedLanguages(languages); } void TranslateInfoBarDelegate::Translate() { @@ -81,11 +81,14 @@ void TranslateInfoBarDelegate::Translate() { // are different, so only in this case is translation really pending. if (original_lang_index_ != target_lang_index_) translation_pending_ = true; - tab_contents_->TranslatePage(original_lang_code(), target_lang_code()); + Singleton<TranslateManager>::get()->TranslatePage(tab_contents_, + original_lang_code(), + target_lang_code()); } void TranslateInfoBarDelegate::RevertTranslation() { - tab_contents_->RevertTranslatedPage(); + Singleton<TranslateManager>::get()->RevertTranslation(tab_contents_); + tab_contents_->RemoveInfoBar(this); } void TranslateInfoBarDelegate::TranslationDeclined() { @@ -177,7 +180,8 @@ string16 TranslateInfoBarDelegate::GetErrorMessage( case TranslateErrors::NETWORK: message_id = IDS_TRANSLATE_INFOBAR_ERROR_CANT_CONNECT; break; - case TranslateErrors::SERVER: + case TranslateErrors::INITIALIZATION_ERROR: + case TranslateErrors::TRANSLATION_ERROR: message_id = IDS_TRANSLATE_INFOBAR_ERROR_CANT_TRANSLATE; break; default: @@ -196,7 +200,7 @@ TranslateInfoBarDelegate* TranslateInfoBarDelegate::Create( const std::string& target_lang_code, TranslateErrors::Type error_type) { std::vector<std::string> supported_languages; - TranslationService::GetSupportedLanguages(&supported_languages); + TranslateManager::GetSupportedLanguages(&supported_languages); int original_lang_index = -1; for (size_t i = 0; i < supported_languages.size(); ++i) { @@ -241,7 +245,7 @@ TranslateInfoBarDelegate::TranslateInfoBarDelegate(TabContents* tab_contents, never_translate_site_(false), always_translate_(false), error_type_(error_type) { - TranslationService::GetSupportedLanguages(&supported_languages_); + TranslateManager::GetSupportedLanguages(&supported_languages_); DCHECK(original_lang_index_ > -1); DCHECK(target_lang_index_ > -1); } diff --git a/chrome/browser/translate/translate_manager.cc b/chrome/browser/translate/translate_manager.cc index 214d731..3968de9 100644 --- a/chrome/browser/translate/translate_manager.cc +++ b/chrome/browser/translate/translate_manager.cc @@ -4,6 +4,7 @@ #include "chrome/browser/translate/translate_manager.h" +#include "app/resource_bundle.h" #include "base/compiler_specific.h" #include "base/string_util.h" #include "chrome/browser/browser_process.h" @@ -11,7 +12,6 @@ #include "chrome/browser/profile.h" #include "chrome/browser/renderer_host/render_process_host.h" #include "chrome/browser/renderer_host/render_view_host.h" -#include "chrome/browser/renderer_host/translation_service.h" #include "chrome/browser/tab_contents/language_state.h" #include "chrome/browser/tab_contents/navigation_controller.h" #include "chrome/browser/tab_contents/navigation_entry.h" @@ -24,9 +24,102 @@ #include "chrome/common/notification_source.h" #include "chrome/common/notification_type.h" #include "chrome/common/pref_names.h" +#include "grit/browser_resources.h" +#include "net/url_request/url_request_status.h" + +namespace { + +// Mapping from a locale name to a language code name. +// Locale names not included are translated as is. +struct LocaleToCLDLanguage { + const char* locale_language; // Language Chrome locale is in. + const char* cld_language; // Language the CLD reports. +}; +LocaleToCLDLanguage kLocaleToCLDLanguages[] = { + { "en-GB", "en" }, + { "en-US", "en" }, + { "es-419", "es" }, + { "pt-BR", "pt" }, + { "pt-PT", "pt" }, +}; + +// The list of languages the Google translation server supports. +// For information, here is the list of languages that Chrome can be run in +// but that the translation server does not support: +// am Amharic +// bn Bengali +// gu Gujarati +// kn Kannada +// ml Malayalam +// mr Marathi +// ta Tamil +// te Telugu +const char* kSupportedLanguages[] = { + "af", // Afrikaans + "sq", // Albanian + "ar", // Arabic + "be", // Belarusian + "bg", // Bulgarian + "ca", // Catalan + "zh-CN", // Chinese (Simplified) + "zh-TW", // Chinese (Traditional) + "hr", // Croatian + "cs", // Czech + "da", // Danish + "nl", // Dutch + "en", // English + "et", // Estonian + "fi", // Finnish + "fil", // Filipino + "fr", // French + "gl", // Galician + "de", // German + "el", // Greek + "he", // Hebrew + "hi", // Hindi + "hu", // Hungarian + "is", // Icelandic + "id", // Indonesian + "it", // Italian + "ga", // Irish + "ja", // Japanese + "ko", // Korean + "lv", // Latvian + "lt", // Lithuanian + "mk", // Macedonian + "ms", // Malay + "mt", // Maltese + "nb", // Norwegian + "fa", // Persian + "pl", // Polish + "pt", // Portuguese + "ro", // Romanian + "ru", // Russian + "sr", // Serbian + "sk", // Slovak + "sl", // Slovenian + "es", // Spanish + "sw", // Swahili + "sv", // Swedish + "th", // Thai + "tr", // Turkish + "uk", // Ukrainian + "vi", // Vietnamese + "cy", // Welsh + "yi", // Yiddish +}; + +const char* const kTranslateScriptURL = + "http://translate.google.com/translate_a/element.js?" + "cb=cr.googleTranslate.onTranslateElementLoad"; +const char* const kTranslateScriptHeader = + "Google-Translate-Element-Mode: library"; + +} // namespace // static -bool TranslateManager::test_enabled_ = false; +base::LazyInstance<std::set<std::string> > + TranslateManager::supported_languages_(base::LINKER_INITIALIZED); TranslateManager::~TranslateManager() { } @@ -36,6 +129,34 @@ bool TranslateManager::IsTranslatableURL(const GURL& url) { return !url.SchemeIs("chrome"); } +// static +void TranslateManager::GetSupportedLanguages( + std::vector<std::string>* languages) { + DCHECK(languages && languages->empty()); + for (size_t i = 0; i < arraysize(kSupportedLanguages); ++i) + languages->push_back(kSupportedLanguages[i]); +} + +// static +std::string TranslateManager::GetLanguageCode( + const std::string& chrome_locale) { + for (size_t i = 0; i < arraysize(kLocaleToCLDLanguages); ++i) { + if (chrome_locale == kLocaleToCLDLanguages[i].locale_language) + return kLocaleToCLDLanguages[i].cld_language; + } + return chrome_locale; +} + +// static +bool TranslateManager::IsSupportedLanguage(const std::string& page_language) { + if (supported_languages_.Pointer()->empty()) { + for (size_t i = 0; i < arraysize(kSupportedLanguages); ++i) + supported_languages_.Pointer()->insert(kSupportedLanguages[i]); + } + return supported_languages_.Pointer()->find(page_language) != + supported_languages_.Pointer()->end(); +} + void TranslateManager::Observe(NotificationType type, const NotificationSource& source, const NotificationDetails& details) { @@ -135,6 +256,48 @@ void TranslateManager::Observe(NotificationType type, } } +void TranslateManager::OnURLFetchComplete(const URLFetcher* source, + const GURL& url, + const URLRequestStatus& status, + int response_code, + const ResponseCookies& cookies, + const std::string& data) { + scoped_ptr<const URLFetcher> delete_ptr(source); + DCHECK(translate_script_request_pending_); + translate_script_request_pending_ = false; + if (status.status() != URLRequestStatus::SUCCESS || response_code != 200) + return; // We could not retrieve the translate script. + + base::StringPiece str = ResourceBundle::GetSharedInstance(). + GetRawDataResource(IDR_TRANSLATE_JS); + DCHECK(translate_script_.empty()); + str.CopyToString(&translate_script_); + translate_script_ += "\n" + data; + + // Execute any pending requests. + std::vector<PendingRequest>::const_iterator iter; + for (iter = pending_requests_.begin(); iter != pending_requests_.end(); + ++iter) { + const PendingRequest& request = *iter; + TabContents* tab = tab_util::GetTabContentsByID(request.render_process_id, + request.render_view_id); + if (!tab) { + // The tab went away while we were retrieving the script. + continue; + } + NavigationEntry* entry = tab->controller().GetActiveEntry(); + if (!entry || entry->page_id() != request.page_id) { + // We navigated away from the page the translation was triggered on. + continue; + } + + // Translate the page. + DoTranslatePage(tab, translate_script_, + request.source_lang, request.target_lang); + } + pending_requests_.clear(); +} + // static bool TranslateManager::IsShowingTranslateInfobar(TabContents* tab) { for (int i = 0; i < tab->infobar_delegate_count(); ++i) { @@ -145,10 +308,8 @@ bool TranslateManager::IsShowingTranslateInfobar(TabContents* tab) { } TranslateManager::TranslateManager() - : ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)) { - if (!test_enabled_ && !TranslationService::IsTranslationEnabled()) - return; - + : ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)), + translate_script_request_pending_(false) { notification_registrar_.Add(this, NotificationType::NAV_ENTRY_COMMITTED, NotificationService::AllSources()); notification_registrar_.Add(this, NotificationType::TAB_LANGUAGE_DETERMINED, @@ -172,8 +333,7 @@ void TranslateManager::InitiateTranslation(TabContents* tab, std::string target_lang = GetTargetLanguage(); // Nothing to do if either the language Chrome is in or the language of the // page is not supported by the translation server. - if (target_lang.empty() || - !TranslationService::IsSupportedLanguage(page_lang)) { + if (target_lang.empty() || !IsSupportedLanguage(page_lang)) { return; } @@ -194,14 +354,14 @@ void TranslateManager::InitiateTranslation(TabContents* tab, // page's text is sent to the translate server. if (TranslatePrefs::ShouldAutoTranslate(prefs, page_lang, target_lang) && !tab->profile()->IsOffTheRecord()) { - tab->TranslatePage(page_lang, target_lang); + TranslatePage(tab, page_lang, target_lang); return; } std::string auto_translate_to = tab->language_state().AutoTranslateTo(); if (!auto_translate_to.empty()) { // This page was navigated through a click from a translated page. - tab->TranslatePage(page_lang, auto_translate_to); + TranslatePage(tab, page_lang, auto_translate_to); return; } @@ -222,6 +382,57 @@ void TranslateManager::InitiateTranslationPosted(int process_id, InitiateTranslation(tab, page_lang); } +void TranslateManager::TranslatePage(TabContents* tab_contents, + const std::string& source_lang, + const std::string& target_lang) { + NavigationEntry* entry = tab_contents->controller().GetActiveEntry(); + if (!entry) { + NOTREACHED(); + return; + } + if (!translate_script_.empty()) { + DoTranslatePage(tab_contents, translate_script_, source_lang, target_lang); + return; + } + + // The script is not available yet. Queue that request and query for the + // script. Once it is downloaded we'll do the translate. + RenderViewHost* rvh = tab_contents->render_view_host(); + PendingRequest request; + request.render_process_id = rvh->process()->id(); + request.render_view_id = rvh->routing_id(); + request.page_id = entry->page_id(); + request.source_lang = source_lang; + request.target_lang = target_lang; + pending_requests_.push_back(request); + RequestTranslateScript(); +} + +void TranslateManager::RevertTranslation(TabContents* tab_contents) { + NavigationEntry* entry = tab_contents->controller().GetActiveEntry(); + if (!entry) { + NOTREACHED(); + return; + } + tab_contents->render_view_host()->RevertTranslation(entry->page_id()); +} + +void TranslateManager::DoTranslatePage(TabContents* tab_contents, + const std::string& translate_script, + const std::string& source_lang, + const std::string& target_lang) { + NavigationEntry* entry = tab_contents->controller().GetActiveEntry(); + if (!entry) { + NOTREACHED(); + return; + } + + tab_contents->language_state().set_translation_pending(true); + tab_contents->render_view_host()->TranslatePage(entry->page_id(), + translate_script, + source_lang, target_lang); +} + bool TranslateManager::IsAcceptLanguage(TabContents* tab, const std::string& language) { PrefService* pref_service = tab->profile()->GetPrefs(); @@ -249,8 +460,8 @@ void TranslateManager::InitAcceptLanguages(PrefService* prefs) { LanguageSet accept_langs_set; SplitString(WideToASCII(accept_langs_str), ',', &accept_langs_list); std::vector<std::string>::const_iterator iter; - std::string ui_lang = TranslationService::GetLanguageCode( - g_browser_process->GetApplicationLocale()); + std::string ui_lang = + GetLanguageCode(g_browser_process->GetApplicationLocale()); bool is_ui_english = StartsWithASCII(ui_lang, "en-", false); for (iter = accept_langs_list.begin(); iter != accept_langs_list.end(); ++iter) { @@ -273,6 +484,18 @@ void TranslateManager::InitAcceptLanguages(PrefService* prefs) { accept_languages_[prefs] = accept_langs_set; } +void TranslateManager::RequestTranslateScript() { + if (translate_script_request_pending_) + return; + + translate_script_request_pending_ = true; + URLFetcher* fetcher = URLFetcher::Create(0, GURL(kTranslateScriptURL), + URLFetcher::GET, this); + fetcher->set_request_context(Profile::GetDefaultRequestContext()); + fetcher->set_extra_request_headers(kTranslateScriptHeader); + fetcher->Start(); +} + // static void TranslateManager::AddTranslateInfoBar( TabContents* tab, TranslateInfoBarDelegate::TranslateState state, @@ -294,9 +517,9 @@ void TranslateManager::AddTranslateInfoBar( // static std::string TranslateManager::GetTargetLanguage() { - std::string target_lang = TranslationService::GetLanguageCode( - g_browser_process->GetApplicationLocale()); - if (TranslationService::IsSupportedLanguage(target_lang)) + std::string target_lang = + GetLanguageCode(g_browser_process->GetApplicationLocale()); + if (IsSupportedLanguage(target_lang)) return target_lang; return std::string(); } diff --git a/chrome/browser/translate/translate_manager.h b/chrome/browser/translate/translate_manager.h index cf0e452..5c7bbb2 100644 --- a/chrome/browser/translate/translate_manager.h +++ b/chrome/browser/translate/translate_manager.h @@ -8,9 +8,12 @@ #include <map> #include <set> #include <string> +#include <vector> +#include "base/lazy_instance.h" #include "base/singleton.h" #include "base/task.h" +#include "chrome/browser/net/url_fetcher.h" #include "chrome/browser/translate/translate_infobars_delegates.h" #include "chrome/common/notification_observer.h" #include "chrome/common/notification_registrar.h" @@ -25,7 +28,8 @@ class TabContents; // page translation the user requests. // It is a singleton. -class TranslateManager : public NotificationObserver { +class TranslateManager : public NotificationObserver, + public URLFetcher::Delegate { public: virtual ~TranslateManager(); @@ -34,6 +38,29 @@ class TranslateManager : public NotificationObserver { const NotificationSource& source, const NotificationDetails& details); + // URLFetcher::Delegate implementation: + virtual void OnURLFetchComplete(const URLFetcher* source, + const GURL& url, + const URLRequestStatus& status, + int response_code, + const ResponseCookies& cookies, + const std::string& data); + + // Translates the page contents from |source_lang| to |target_lang|. + // The actual translation might be performed asynchronously if the translate + // script is not yet available. + void TranslatePage(TabContents* tab_contents, + const std::string& source_lang, + const std::string& target_lang); + + // Reverts the contents of the page in |tab_contents| to its original + // language. + void RevertTranslation(TabContents* tab_contents); + + // Clears the translate script, so it will be fetched next time we translate. + // Currently used by unit-tests. + void ClearTranslateScript() { translate_script_.clear(); } + // Convenience method to know if a tab is showing a translate infobar. static bool IsShowingTranslateInfobar(TabContents* tab); @@ -41,9 +68,16 @@ class TranslateManager : public NotificationObserver { // (chrome:// and others). static bool IsTranslatableURL(const GURL& url); - // Used by unit-test to enable the TranslateManager for testing purpose. - static void set_test_enabled(bool enabled) { test_enabled_ = enabled; } - static bool test_enabled() { return test_enabled_; } + // Fills |languages| with the list of languages that the translate server can + // translate to and from. + static void GetSupportedLanguages(std::vector<std::string>* languages); + + // Returns the language code that can be used with the Translate method for a + // specified |chrome_locale|. + static std::string GetLanguageCode(const std::string& chrome_locale); + + // Returns true if |page_language| is supported by the translation server. + static bool IsSupportedLanguage(const std::string& page_language); protected: TranslateManager(); @@ -51,6 +85,17 @@ class TranslateManager : public NotificationObserver { private: friend struct DefaultSingletonTraits<TranslateManager>; + // Structure that describes a translate request. + // Translation may be deferred while the translate script is being retrieved + // from the translate server. + struct PendingRequest { + int render_process_id; + int render_view_id; + int page_id; + std::string source_lang; + std::string target_lang; + }; + // Starts the translation process on |tab| containing the page in the // |page_lang| language. void InitiateTranslation(TabContents* tab, const std::string& page_lang); @@ -61,6 +106,12 @@ class TranslateManager : public NotificationObserver { int render_id, const std::string& page_lang); + // Sends a translation request to the RenderView of |tab_contents|. + void DoTranslatePage(TabContents* tab_contents, + const std::string& translate_script, + const std::string& source_lang, + const std::string& target_lang); + // Returns true if the passed language has been configured by the user as an // accept language. bool IsAcceptLanguage(TabContents* tab, const std::string& language); @@ -69,6 +120,10 @@ class TranslateManager : public NotificationObserver { // preference in |prefs|. void InitAcceptLanguages(PrefService* prefs); + // Fetches the JS translate script (the script that is injected in the page + // to translate it). + void RequestTranslateScript(); + // Convenience method that adds a translate infobar to |tab|. static void AddTranslateInfoBar( TabContents* tab, @@ -92,7 +147,19 @@ class TranslateManager : public NotificationObserver { ScopedRunnableMethodFactory<TranslateManager> method_factory_; - static bool test_enabled_; + // The JS injected in the page to do the translation. + std::string translate_script_; + + // Whether the translate JS is currently being retrieved. + bool translate_script_request_pending_; + + // The list of pending translate requests. Translate requests are queued when + // the translate script is not ready and has to be fetched from the translate + // server. + std::vector<PendingRequest> pending_requests_; + + // The languages supported by the translation server. + static base::LazyInstance<std::set<std::string> > supported_languages_; DISALLOW_COPY_AND_ASSIGN(TranslateManager); }; diff --git a/chrome/browser/translate/translate_manager_unittest.cc b/chrome/browser/translate/translate_manager_unittest.cc index bc0924c..e543af0 100644 --- a/chrome/browser/translate/translate_manager_unittest.cc +++ b/chrome/browser/translate/translate_manager_unittest.cc @@ -4,6 +4,7 @@ #include "chrome/browser/renderer_host/test/test_render_view_host.h" +#include "chrome/browser/net/test_url_fetcher_factory.h" #include "chrome/browser/renderer_host/mock_render_process_host.h" #include "chrome/browser/tab_contents/render_view_context_menu.h" #include "chrome/browser/translate/translate_infobars_delegates.h" @@ -50,11 +51,15 @@ class TranslateManagerTest : public RenderViewHostTestHarness, process()->sink().GetFirstMessageMatching(ViewMsg_TranslatePage::ID); if (!message) return false; - Tuple3<int, std::string, std::string> translate_param; + Tuple4<int, std::string, std::string, std::string> translate_param; ViewMsg_TranslatePage::Read(message, &translate_param); - *page_id = translate_param.a; - *original_lang = translate_param.b; - *target_lang = translate_param.c; + if (page_id) + *page_id = translate_param.a; + // Ignore translate_param.b which is the script injected in the page. + if (original_lang) + *original_lang = translate_param.c; + if (target_lang) + *target_lang = translate_param.d; return true; } @@ -111,15 +116,15 @@ class TranslateManagerTest : public RenderViewHostTestHarness, protected: virtual void SetUp() { - TranslateManager::set_test_enabled(true); - // This must be created after set_test_enabled() has been called to register - // notifications properly. Note that we do this before calling + URLFetcher::set_factory(&url_fetcher_factory_); + + // Access the TranslateManager singleton so it is created before we call // RenderViewHostTestHarness::SetUp() to match what's done in Chrome, where - // the TranslateManager is created before the TabContents. This matters for - // as they both register for similar events and we want the notifications - // to happen in the same sequence (TranslateManager first, TabContents - // second). - translate_manager_.reset(new TestTranslateManager()); + // the TranslateManager is created before the TabContents. This matters as + // they both register for similar events and we want the notifications to + // happen in the same sequence (TranslateManager first, TabContents second). + // Also clears the translate script so it is fetched everytime. + Singleton<TranslateManager>::get()->ClearTranslateScript(); RenderViewHostTestHarness::SetUp(); @@ -137,13 +142,25 @@ class TranslateManagerTest : public RenderViewHostTestHarness, RenderViewHostTestHarness::TearDown(); - TranslateManager::set_test_enabled(false); + URLFetcher::set_factory(NULL); + } + + void SimulateURLFetch(bool success) { + TestURLFetcher* fetcher = url_fetcher_factory_.GetFetcherByID(0); + ASSERT_TRUE(fetcher); + URLRequestStatus status; + status.set_status(success ? URLRequestStatus::SUCCESS : + URLRequestStatus::FAILED); + fetcher->delegate()->OnURLFetchComplete(fetcher, fetcher->original_url(), + status, success ? 200 : 500, + ResponseCookies(), + std::string()); } private: NotificationRegistrar notification_registrar_; - scoped_ptr<TestTranslateManager> translate_manager_; + TestURLFetcherFactory url_fetcher_factory_; // The list of infobars that have been removed. // WARNING: the pointers points to deleted objects, use only for comparison. @@ -251,6 +268,9 @@ TEST_F(TranslateManagerTest, NormalTranslate) { // Simulate clicking translate. process()->sink().ClearMessages(); infobar->Translate(); + // Simulate the translate script being retrieved (it only needs to be done + // once in the test as it is cached). + SimulateURLFetch(true); EXPECT_FALSE(InfoBarRemoved()); // Test that we sent the right message to the renderer. @@ -297,6 +317,26 @@ TEST_F(TranslateManagerTest, NormalTranslate) { EXPECT_EQ(new_target_lang, target_lang); } +TEST_F(TranslateManagerTest, TranslateScriptNotAvailable) { + // Simulate navigating to a page. + SimulateNavigation(GURL("http://www.google.fr"), 0, L"Le Google", "fr"); + + // We should have an info-bar. + TranslateInfoBarDelegate* infobar = GetTranslateInfoBar(); + ASSERT_TRUE(infobar != NULL); + EXPECT_EQ(TranslateInfoBarDelegate::kBeforeTranslate, infobar->state()); + + // Simulate clicking translate. + process()->sink().ClearMessages(); + infobar->Translate(); + // Simulate a failure retrieving the translate script. + SimulateURLFetch(false); + EXPECT_FALSE(InfoBarRemoved()); + + // We should not have sent any message to translate to the renderer. + EXPECT_FALSE(GetTranslateMessage(NULL, NULL, NULL)); +} + // Tests that we show/don't show an info-bar for all languages the CLD can // report. TEST_F(TranslateManagerTest, TestAllLanguages) { @@ -376,6 +416,8 @@ TEST_F(TranslateManagerTest, AutoTranslateOnNavigate) { TranslateInfoBarDelegate* infobar = GetTranslateInfoBar(); ASSERT_TRUE(infobar != NULL); infobar->Translate(); + SimulateURLFetch(true); // Simulate the translate script being retrieved. + rvh()->TestOnMessageReceived(ViewHostMsg_PageTranslated(0, 0, "fr", "en", TranslateErrors::NONE)); @@ -519,6 +561,7 @@ TEST_F(TranslateManagerTest, TranslateCloseInfoBarInPageNavigation) { TranslateInfoBarDelegate* infobar = GetTranslateInfoBar(); ASSERT_TRUE(infobar != NULL); infobar->Translate(); + SimulateURLFetch(true); // Simulate the translate script being retrieved. rvh()->TestOnMessageReceived(ViewHostMsg_PageTranslated(0, 0, "fr", "en", TranslateErrors::NONE)); @@ -547,6 +590,7 @@ TEST_F(TranslateManagerTest, TranslateInPageNavigation) { TranslateInfoBarDelegate* infobar = GetTranslateInfoBar(); ASSERT_TRUE(infobar != NULL); infobar->Translate(); + SimulateURLFetch(true); // Simulate the translate script being retrieved. rvh()->TestOnMessageReceived(ViewHostMsg_PageTranslated(0, 0, "fr", "en", TranslateErrors::NONE)); @@ -711,6 +755,7 @@ TEST_F(TranslateManagerTest, AlwaysTranslateLanguagePref) { SimulateNavigation(GURL("http://www.google.fr"), 0, L"Le Google", "fr"); // It should have triggered an automatic translation to English. + SimulateURLFetch(true); // Simulate the translate script being retrieved. int page_id = 0; std::string original_lang, target_lang; EXPECT_TRUE(GetTranslateMessage(&page_id, &original_lang, &target_lang)); @@ -769,6 +814,7 @@ TEST_F(TranslateManagerTest, ContextMenu) { menu->TestExecuteItemCommand(IDS_CONTENT_CONTEXT_TRANSLATE); // That should have triggered a translation. + SimulateURLFetch(true); // Simulate the translate script being retrieved. int page_id = 0; std::string original_lang, target_lang; EXPECT_TRUE(GetTranslateMessage(&page_id, &original_lang, &target_lang)); |