diff options
36 files changed, 1050 insertions, 2745 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)); diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi index 6b23d1f..1ba6cfd 100644 --- a/chrome/chrome_browser.gypi +++ b/chrome/chrome_browser.gypi @@ -76,7 +76,7 @@ 'browser/appcache/chrome_appcache_service.cc', 'browser/appcache/chrome_appcache_service.h', 'browser/appcache/view_appcache_internals_job_factory.cc', - 'browser/appcache/view_appcache_internals_job_factory.h', + 'browser/appcache/view_appcache_internals_job_factory.h', 'browser/autocomplete/autocomplete.cc', 'browser/autocomplete/autocomplete.h', 'browser/autocomplete/autocomplete_accessibility.cc', @@ -1830,8 +1830,6 @@ 'browser/renderer_host/socket_stream_host.h', 'browser/renderer_host/sync_resource_handler.cc', 'browser/renderer_host/sync_resource_handler.h', - 'browser/renderer_host/translation_service.cc', - 'browser/renderer_host/translation_service.h', 'browser/renderer_host/video_layer.cc', 'browser/renderer_host/video_layer.h', 'browser/renderer_host/video_layer_proxy.cc', diff --git a/chrome/chrome_renderer.gypi b/chrome/chrome_renderer.gypi index 9e80e36..7598f35 100755 --- a/chrome/chrome_renderer.gypi +++ b/chrome/chrome_renderer.gypi @@ -145,11 +145,8 @@ 'renderer/spellchecker/spellcheck.h', 'renderer/spellchecker/spellcheck_worditerator.cc', 'renderer/spellchecker/spellcheck_worditerator.h', - 'renderer/translate/text_translator.h', - 'renderer/translate/text_translator_impl.cc', - 'renderer/translate/text_translator_impl.h', - 'renderer/translate/page_translator.cc', - 'renderer/translate/page_translator.h', + 'renderer/translate_helper.cc', + 'renderer/translate_helper.h', 'renderer/user_script_idle_scheduler.cc', 'renderer/user_script_idle_scheduler.h', 'renderer/user_script_slave.cc', diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi index 50129c7..7aca9a3 100644 --- a/chrome/chrome_tests.gypi +++ b/chrome/chrome_tests.gypi @@ -844,7 +844,6 @@ 'browser/renderer_host/resource_queue_unittest.cc', 'browser/renderer_host/test/render_view_host_unittest.cc', 'browser/renderer_host/test/site_instance_unittest.cc', - 'browser/renderer_host/translation_service_unittest.cc', 'browser/renderer_host/web_cache_manager_unittest.cc', 'browser/rlz/rlz_unittest.cc', 'browser/safe_browsing/bloom_filter_unittest.cc', @@ -969,7 +968,7 @@ 'renderer/renderer_main_unittest.cc', 'renderer/spellchecker/spellcheck_unittest.cc', 'renderer/spellchecker/spellcheck_worditerator_unittest.cc', - 'renderer/translate/page_translator_unittest.cc', + 'renderer/translate_helper_unittest.cc', 'test/browser_with_test_window_test.cc', 'test/browser_with_test_window_test.h', 'test/file_test_utils.cc', @@ -1211,7 +1210,7 @@ 'test/test_launcher/test_runner.h', 'test/unit/chrome_test_suite.h', 'browser/chromeos/login/wizard_in_process_browser_test.cc', - 'browser/chromeos/login/wizard_in_process_browser_test.h', + 'browser/chromeos/login/wizard_in_process_browser_test.h', # Actual test sources 'browser/autocomplete/autocomplete_browsertest.cc', 'browser/browser_browsertest.cc', @@ -1344,7 +1343,7 @@ 'browser/chromeos/compact_location_bar_host_browsertest.cc', 'browser/chromeos/compact_navigation_bar_browsertest.cc', 'browser/chromeos/cros/cros_in_process_browser_test.cc', - 'browser/chromeos/cros/cros_in_process_browser_test.h', + 'browser/chromeos/cros/cros_in_process_browser_test.h', 'browser/chromeos/cros/mock_mount_library.cc', 'browser/chromeos/cros/mock_mount_library.h', 'browser/chromeos/login/account_screen_browsertest.cc', diff --git a/chrome/common/render_messages.h b/chrome/common/render_messages.h index 211297a..c76b430 100644 --- a/chrome/common/render_messages.h +++ b/chrome/common/render_messages.h @@ -595,34 +595,6 @@ struct ViewMsg_New_Params { int64 session_storage_namespace_id; }; -// Message to ask the browser to translate some text from one language to -// another. -struct ViewHostMsg_TranslateTextParam { - // The routing id. Even though ViewHostMsg_TranslateText is a control message - // (sent to the browser, not to a specific RenderViewHost), the browser needs - // the routing id in order to send the response back to the right RenderView. - int routing_id; - - // An id used to identify that specific translation. - int work_id; - - // The id of the page this translation originated from. - int page_id; - - // The text chunks that need to be translated. - std::vector<string16> text_chunks; - - // The ISO code of the language the text to translate is in. - std::string from_language; - - // The ISO code of the language the text should be translated to. - std::string to_language; - - // Whether a secure connection should be used when transmitting the text for - // translation to an external server. - bool secure; -}; - struct ViewHostMsg_RunFileChooser_Params { enum Mode { // Requires that the file exists before allowing the user to pick it. @@ -2569,48 +2541,6 @@ struct ParamTraits<ViewMsg_New_Params> { } }; -template<> -struct ParamTraits<ViewHostMsg_TranslateTextParam> { - typedef ViewHostMsg_TranslateTextParam param_type; - static void Write(Message* m, const param_type& p) { - WriteParam(m, p.routing_id); - WriteParam(m, p.work_id); - WriteParam(m, p.page_id); - WriteParam(m, p.text_chunks); - WriteParam(m, p.from_language); - WriteParam(m, p.to_language); - WriteParam(m, p.secure); - } - - static bool Read(const Message* m, void** iter, param_type* p) { - return - ReadParam(m, iter, &p->routing_id) && - ReadParam(m, iter, &p->work_id) && - ReadParam(m, iter, &p->page_id) && - ReadParam(m, iter, &p->text_chunks) && - ReadParam(m, iter, &p->from_language) && - ReadParam(m, iter, &p->to_language) && - ReadParam(m, iter, &p->secure); - } - static void Log(const param_type& p, std::wstring* l) { - l->append(L"("); - LogParam(p.routing_id, l); - l->append(L", "); - LogParam(p.work_id, l); - l->append(L", "); - LogParam(p.page_id, l); - l->append(L", "); - LogParam(p.text_chunks, l); - l->append(L", "); - LogParam(p.from_language, l); - l->append(L", "); - LogParam(p.to_language, l); - l->append(L", "); - LogParam(p.secure, l); - l->append(L")"); - } -}; - template <> struct SimilarTypeTraits<TranslateErrors::Type> { typedef int Type; diff --git a/chrome/common/render_messages_internal.h b/chrome/common/render_messages_internal.h index b1222f1..68d69d0 100644 --- a/chrome/common/render_messages_internal.h +++ b/chrome/common/render_messages_internal.h @@ -896,19 +896,18 @@ IPC_BEGIN_MESSAGES(View) // Tells the renderer to translate the page contents from one language to // another. - IPC_MESSAGE_ROUTED3(ViewMsg_TranslatePage, + IPC_MESSAGE_ROUTED4(ViewMsg_TranslatePage, int /* page id */, + std::string, /* the script injected in the page */ std::string, /* BCP 47/RFC 5646 language code the page is in */ std::string /* BCP 47/RFC 5646 language code to translate to */) - // Reply to the ViewHostMsg_TranslateText message with the actual translated - // text chunks. - IPC_MESSAGE_ROUTED3(ViewMsg_TranslateTextReponse, - int /* id of translation work */, - int /* error id of translation work */, - std::vector<string16> /* the translated text chunks */) + // Tells the renderer to revert the text of translated page to its original + // contents. + IPC_MESSAGE_ROUTED1(ViewMsg_RevertTranslation, + int /* page id */) // Reply in response to ViewHostMsg_Geolocation_RequestPermission. IPC_MESSAGE_ROUTED2(ViewMsg_Geolocation_PermissionSet, @@ -2227,11 +2226,6 @@ IPC_BEGIN_MESSAGES(ViewHost) string16 /* word */, std::vector<string16> /* suggestions */) - // Request for text translation. - // Used when translating a page from one language to another. - IPC_MESSAGE_CONTROL1(ViewHostMsg_TranslateText, - ViewHostMsg_TranslateTextParam) - //--------------------------------------------------------------------------- // Geolocation services messages diff --git a/chrome/common/translate_errors.h b/chrome/common/translate_errors.h index de2e3bd..c8f4225 100644 --- a/chrome/common/translate_errors.h +++ b/chrome/common/translate_errors.h @@ -11,8 +11,10 @@ class TranslateErrors { public: enum Type { NONE = 0, - NETWORK = 1, - SERVER, + NETWORK, // No connectivity. + INITIALIZATION_ERROR, // The translation script failed to initialize. + TRANSLATION_ERROR, // An error was reported by the translation script + // during translation. }; private: diff --git a/chrome/renderer/render_view.cc b/chrome/renderer/render_view.cc index cd649e7..6cfb152 100644 --- a/chrome/renderer/render_view.cc +++ b/chrome/renderer/render_view.cc @@ -343,12 +343,11 @@ RenderView::RenderView(RenderThreadBase* render_thread, document_tag_(0), webkit_preferences_(webkit_preferences), session_storage_namespace_id_(session_storage_namespace_id), - ALLOW_THIS_IN_INITIALIZER_LIST(text_translator_(this)), ALLOW_THIS_IN_INITIALIZER_LIST(cookie_jar_(this)), + ALLOW_THIS_IN_INITIALIZER_LIST(translate_helper_(this)), cross_origin_access_count_(0), same_origin_access_count_(0) { ClearBlockedContentSettings(); - page_translator_.reset(new PageTranslator(&text_translator_, this)); } RenderView::~RenderView() { @@ -633,7 +632,7 @@ void RenderView::OnMessageReceived(const IPC::Message& message) { IPC_MESSAGE_HANDLER(ViewMsg_CustomContextMenuAction, OnCustomContextMenuAction) IPC_MESSAGE_HANDLER(ViewMsg_TranslatePage, OnTranslatePage) - IPC_MESSAGE_HANDLER(ViewMsg_TranslateTextReponse, OnTranslateTextResponse) + IPC_MESSAGE_HANDLER(ViewMsg_RevertTranslation, OnRevertTranslation) // Have the super handle all other messages. IPC_MESSAGE_UNHANDLED(RenderWidget::OnMessageReceived(message)) @@ -2522,12 +2521,6 @@ void RenderView::didCommitProvisionalLoad(WebFrame* frame, NavigationState* navigation_state = NavigationState::FromDataSource(frame->dataSource()); - if (!frame->parent()) { // Main frame case. - // Let the page translator know that the page has changed so it can clear - // its states. - page_translator_->MainFrameNavigated(); - } - navigation_state->set_commit_load_time(Time::Now()); if (is_new_navigation) { // When we perform a new navigation, we need to update the previous session @@ -2653,9 +2646,6 @@ void RenderView::didFinishDocumentLoad(WebFrame* frame) { } navigation_state->user_script_idle_scheduler()->DidFinishDocumentLoad(); - - if (page_translator_->IsPageTranslated()) - page_translator_->TranslateFrame(frame); } void RenderView::OnUserScriptIdleTriggered(WebFrame* frame) { @@ -3182,15 +3172,6 @@ WebCookieJar* RenderView::GetCookieJar() { return &cookie_jar_; } -void RenderView::PageTranslated(int page_id, - const std::string& original_lang, - const std::string& target_lang, - TranslateErrors::Type error_type) { - Send(new ViewHostMsg_PageTranslated(routing_id_, page_id_, - original_lang, target_lang, - error_type)); -} - void RenderView::SyncNavigationState() { if (!webview()) return; @@ -3808,22 +3789,15 @@ void RenderView::OnCustomContextMenuAction(unsigned action) { } void RenderView::OnTranslatePage(int page_id, + const std::string& translate_script, const std::string& source_lang, const std::string& target_lang) { - if (page_id != page_id_) - return; // Not the page we expected, nothing to do. - - WebFrame* main_frame = webview()->mainFrame(); - if (!main_frame) - return; - - page_translator_->TranslatePage(page_id, main_frame, - source_lang, target_lang); + translate_helper_.TranslatePage(page_id, source_lang, target_lang, + translate_script); } -void RenderView::OnTranslateTextResponse( - int work_id, int error_id, const std::vector<string16>& text_chunks) { - text_translator_.OnTranslationResponse(work_id, error_id, text_chunks); +void RenderView::OnRevertTranslation(int page_id) { + translate_helper_.RevertTranslation(page_id); } void RenderView::OnInstallMissingPlugin() { diff --git a/chrome/renderer/render_view.h b/chrome/renderer/render_view.h index ffd2ebb..cd42daf 100644 --- a/chrome/renderer/render_view.h +++ b/chrome/renderer/render_view.h @@ -40,8 +40,7 @@ #include "chrome/renderer/render_widget.h" #include "chrome/renderer/render_view_visitor.h" #include "chrome/renderer/renderer_webcookiejar_impl.h" -#include "chrome/renderer/translate/page_translator.h" -#include "chrome/renderer/translate/text_translator_impl.h" +#include "chrome/renderer/translate_helper.h" #include "gfx/point.h" #include "gfx/rect.h" #include "third_party/skia/include/core/SkBitmap.h" @@ -135,8 +134,7 @@ class RenderView : public RenderWidget, public WebKit::WebFrameClient, public WebKit::WebPageSerializerClient, public webkit_glue::WebPluginPageDelegate, - public base::SupportsWeakPtr<RenderView>, - public PageTranslator::PageTranslatorDelegate { + public base::SupportsWeakPtr<RenderView> { public: // Visit all RenderViews with a live WebView (i.e., RenderViews that have // been closed but not yet destroyed are excluded). @@ -418,12 +416,6 @@ class RenderView : public RenderWidget, std::string* json_retval); virtual WebKit::WebCookieJar* GetCookieJar(); - // PageTranslator::PageTranslatorDelegate implementation: - virtual void PageTranslated(int page_id, - const std::string& original_lang, - const std::string& target_lang, - TranslateErrors::Type error_type); - // Do not delete directly. This class is reference counted. virtual ~RenderView(); @@ -494,8 +486,6 @@ class RenderView : public RenderWidget, // UserScript::DOCUMENT_IDLE. void OnUserScriptIdleTriggered(WebKit::WebFrame* frame); - PageTranslator* page_translator() const { return page_translator_.get(); } - #if defined(OS_MACOSX) // Helper routines for GPU plugin support. Used by the // WebPluginDelegateProxy, which has a pointer to the RenderView. @@ -800,15 +790,15 @@ class RenderView : public RenderWidget, // Execute custom context menu action. void OnCustomContextMenuAction(unsigned action); - // Tells the renderer to translate the page contents. + // Translates the page contents from |source_lang| to |target_lang| by + // injecting |translate_script| in the page. void OnTranslatePage(int page_id, + const std::string& translate_script, const std::string& source_lang, const std::string& target_lang); - // Message that provides the translated text for a request. - void OnTranslateTextResponse(int work_id, - int error_id, - const std::vector<string16>& text_chunks); + // Reverts the page's text to its original contents. + void OnRevertTranslation(int page_id); // Exposes the DOMAutomationController object that allows JS to send // information to the browser process. @@ -1183,10 +1173,6 @@ class RenderView : public RenderWidget, // uses it whenever asking the browser process to allocate new storage areas. int64 session_storage_namespace_id_; - // Page translation related objects. - TextTranslatorImpl text_translator_; - scoped_ptr<PageTranslator> page_translator_; - // A list of all pepper plugins that we've created that haven't been // destroyed yet. std::set<WebPluginDelegatePepper*> current_pepper_plugins_; @@ -1206,6 +1192,10 @@ class RenderView : public RenderWidget, RendererWebCookieJarImpl cookie_jar_; + // The object responsible for translating the page contents to other + // languages. + TranslateHelper translate_helper_; + // Site isolation metrics flags. These are per-page-load counts, reset to 0 // in OnClosePage. int cross_origin_access_count_; diff --git a/chrome/renderer/translate/page_translator.cc b/chrome/renderer/translate/page_translator.cc deleted file mode 100644 index cba0001..0000000 --- a/chrome/renderer/translate/page_translator.cc +++ /dev/null @@ -1,324 +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/renderer/translate/page_translator.h" - -#include "base/compiler_specific.h" -#include "base/message_loop.h" -#include "base/stl_util-inl.h" -#include "base/string_util.h" -#include "base/task.h" -#include "chrome/renderer/navigation_state.h" -#include "third_party/WebKit/WebKit/chromium/public/WebDocument.h" -#include "third_party/WebKit/WebKit/chromium/public/WebDataSource.h" -#include "third_party/WebKit/WebKit/chromium/public/WebElement.h" -#include "third_party/WebKit/WebKit/chromium/public/WebFrame.h" -#include "third_party/WebKit/WebKit/chromium/public/WebNode.h" -#include "third_party/WebKit/WebKit/chromium/public/WebNodeList.h" -#include "third_party/WebKit/WebKit/chromium/public/WebString.h" - -namespace { - -// The following elements are not supposed to be translated. -const char* const kSkippedTags[] = { "APPLET", "AREA", "BASE", "FRAME", - "FRAMESET", "HR", "IFRAME", "IMG", "INPUT", "LINK", "META", "MAP", - "OBJECT", "PARAM", "SCRIPT", "STYLE", "TEXTAREA" }; - -// The following tags are not considered as breaking a block of text. -// Notes: does SPAN belong to this list? -const char* const kInlineTags[] = { "A", "ABBR", "ACRONYM", "B", "BIG", "DEL", - "EM", "I", "INS", "S", "SPAN", "STRIKE", "STRONG", "SUB", "SUP", "U" }; -} - -// A text node containing only characters in kIgnoredCharacters is not -// translated. -const char* const kIgnoredCharacters = ":,.[|]0123456789"; - -// Returns true when s1 < s2. -bool PageTranslator::WebStringCompare::operator()( - const WebKit::WebString& s1, const WebKit::WebString& s2) const { - int len1 = s1.length(); - int len2 = s2.length(); - int r = base::strncmp16(s1.data(), s2.data(), std::min(len1, len2)); - - if (r < 0) - return true; - else if (r > 0) - return false; - - return len1 < len2; -} - -PageTranslator::PageTranslator(TextTranslator* text_translator, - PageTranslatorDelegate* delegate) - : delegate_(delegate), - text_translator_(text_translator), - page_id_(-1), - secure_page_(false) { - for (size_t i = 0; i < arraysize(kSkippedTags); ++i) - ignored_tags_.insert(WebKit::WebString(ASCIIToUTF16(kSkippedTags[i]))); - for (size_t i = 0; i < arraysize(kInlineTags); ++i) - inline_tags_.insert(WebKit::WebString(ASCIIToUTF16(kInlineTags[i]))); - ignore_characters_ = ASCIIToUTF16(kIgnoredCharacters); - ignore_characters_.append(kWhitespaceUTF16); -} - -PageTranslator::~PageTranslator() { - ResetPageStates(); -} - -void PageTranslator::TranslatePage(int page_id, - WebKit::WebFrame* main_frame, - std::string source_lang, - std::string target_lang) { - if (page_id != page_id_) { - // This is a new page, our states are invalid. - ResetPageStates(); - page_id_ = page_id; - secure_page_ = static_cast<GURL>(main_frame->top()->url()).SchemeIsSecure(); - - original_language_ = source_lang; - current_language_ = target_lang; - - // Translate all frames contained within the main-frame. - for (WebKit::WebFrame* frame = main_frame; - frame; frame = frame->traverseNext(false)) { - TranslateFrame(frame); - } - return; - } - - // The page has already been translated, the text nodes are already available. - DCHECK(!text_nodes_.empty()); - - if (target_lang == original_language_) { - // Special case where we want to revert to the original language. - RevertTranslation(); - return; - } - - // Any pending translation is now useless. - ClearPendingTranslations(); - // No need to parse again the DOM, we have all the text nodes and their - // original text in |text_nodes_| and |text_chunks_|. - std::vector<NodeList*>::iterator text_nodes_iter = text_nodes_.begin(); - std::vector<TextChunks*>::iterator text_chunks_iter = text_chunks_.begin(); - for (;text_nodes_iter != text_nodes_.end(); - ++text_nodes_iter, ++text_chunks_iter) { - DCHECK(text_chunks_iter != text_chunks_.end()); - int work_id = text_translator_->Translate(**text_chunks_iter, - source_lang, target_lang, - secure_page_, this); - pending_translations_[work_id] = *text_nodes_iter; - } - current_language_ = target_lang; -} - -void PageTranslator::TranslateFrame(WebKit::WebFrame* web_frame) { - if (page_id_ == -1) - return; // The page has not been translated, ignore. - - DCHECK(!original_language_.empty() && !current_language_.empty()); - - WebKit::WebDataSource* ds = web_frame->dataSource(); - NavigationState* navigation_state = NavigationState::FromDataSource(ds); - DCHECK(navigation_state); - if (navigation_state->was_translated()) - return; // This frame has already been translated, nothing to do. - - // If the frame has no document or an empty document, it may not have been - // loaded yet. - if (web_frame->document().isNull() || - web_frame->document().childNodes().length() == 0) { - return; - } - - std::stack<NodeList*> node_list_stack; - std::vector<NodeList*> text_node_lists; - TraverseNode(web_frame->document(), &node_list_stack, &text_node_lists); - - std::vector<NodeList*>::iterator iter; - for (iter = text_node_lists.begin(); iter != text_node_lists.end(); ++iter) { - if ((*iter)->empty()) { - // Nothing to translate. - continue; - } - TextChunks* text_chunks = new TextChunks; // The text chunks to translate. - NodeList::iterator text_nodes_iter; - for (text_nodes_iter = (*iter)->begin(); - text_nodes_iter != (*iter)->end(); ++text_nodes_iter) { - DCHECK(text_nodes_iter->isTextNode()); - string16 text = static_cast<string16>(text_nodes_iter->nodeValue()); - DCHECK(!ContainsOnlyWhitespace(text)); - text_chunks->push_back(text); - } - - // Send the text for translation. - int work_id = - text_translator_->Translate(*text_chunks, - original_language_, current_language_, - secure_page_, this); - pending_translations_[work_id] = *iter; - // Also store the text nodes and their original text so we can translate to - // another language if necessary. - text_nodes_.push_back(*iter); - text_chunks_.push_back(text_chunks); - } - - navigation_state->set_was_translated(true); -} - -void PageTranslator::MainFrameNavigated() { - // We can drop all our states, they were related to the previous page. - ResetPageStates(); -} - -bool PageTranslator::IsPageTranslated() { - return original_language_ != current_language_; -} - -bool PageTranslator::ShouldElementBeTraversed(WebKit::WebElement element) { - return ignored_tags_.find(element.tagName()) == ignored_tags_.end(); -} - -bool PageTranslator::IsInlineElement(WebKit::WebElement element) { - return inline_tags_.find(element.tagName()) != inline_tags_.end(); -} - -void PageTranslator::ClearNodeZone(int work_id) { - std::map<int, NodeList*>::iterator iter = pending_translations_.find(work_id); - if (iter == pending_translations_.end()) { - NOTREACHED() << "Clearing unknown node zone in pending_translations_, " - "work id=" << work_id; - return; - } - pending_translations_.erase(iter); -} - -void PageTranslator::TranslationError(int work_id, int error_id) { - // TODO(jcampan): may be we should show somehow that something went wrong to - // the user? - ClearNodeZone(work_id); -} - -void PageTranslator::TextTranslated( - int work_id, const std::vector<string16>& translated_text_chunks) { - std::map<int, NodeList*>::iterator iter = pending_translations_.find(work_id); - if (iter == pending_translations_.end()) { - // We received some translated text we were not expecting. It could be we - // navigated away from the page or that the translation was undone. - return; - } - - NodeList* nodes = iter->second; - // Check the integrity of the response. - if (translated_text_chunks.size() != nodes->size()) { - // The server might merge or split chunks in some cases. - // TODO(jcampan): once the issue is resolved on the server, reenable that - // NOTREACHED(). - // NOTREACHED() << "Translation results received are inconsistent with the " - // "request"; - LOG(ERROR) << "translation response for work id " << work_id << - " length is " << translated_text_chunks.size() << " expected " << - nodes->size(); - ClearNodeZone(work_id); - return; - } - - for (size_t i = 0; i < translated_text_chunks.size(); ++i) - (*nodes)[i].setNodeValue(WebKit::WebString(translated_text_chunks[i])); - - ClearNodeZone(work_id); - - if (delegate_ && pending_translations_.empty()) { - // TODO(jcivelli): if there's error, pass in the actual error type. - TranslateErrors::Type error_type = TranslateErrors::NONE; - delegate_->PageTranslated(page_id_, original_language_, current_language_, - error_type); - } -} - -void PageTranslator::TraverseNode(WebKit::WebNode node, - std::stack<NodeList*>* element_stack, - std::vector<NodeList*>* text_nodes_list) { - if (node.isTextNode()) { - string16 text = static_cast<string16>(node.nodeValue()); - if (ContainsOnlyChars(text, ignore_characters_)) - return; // Ignore text nodes which contains only white-spaces or - // separators. - - DCHECK(!element_stack->empty()); - NodeList* text_nodes = element_stack->top(); - if (text_nodes->empty()) { - // This node zone is empty, meaning it has not yet been added to - // |text_nodes|. - text_nodes_list->push_back(text_nodes); - } - text_nodes->push_back(node); - return; - } - - if (!node.hasChildNodes()) - return; - - bool new_text_block = false; - if (node.isElementNode()) { - WebKit::WebElement element = node.toElement<WebKit::WebElement>(); - if (!ShouldElementBeTraversed(element)) - return; - - if (!IsInlineElement(element)) { - new_text_block = true; - NodeList* text_nodes = new NodeList(); - element_stack->push(text_nodes); - } - } - - WebKit::WebNodeList children = node.childNodes(); - for (size_t i = 0; i < children.length(); i++) - TraverseNode(children.item(i), element_stack, text_nodes_list); - - if (new_text_block) { - NodeList* text_nodes = element_stack->top(); - element_stack->pop(); - // If no nodes were added to text_nodes, then it has not been added to - // text_nodes_list and must be deleted. - if (text_nodes->empty()) - delete text_nodes; - } -} - -void PageTranslator::ResetPageStates() { - page_id_ = -1; - secure_page_ = false; - STLDeleteElements(&text_nodes_); - STLDeleteElements(&text_chunks_); - original_language_.clear(); - current_language_.clear(); - ClearPendingTranslations(); -} - -void PageTranslator::ClearPendingTranslations() { - pending_translations_.clear(); -} - -void PageTranslator::RevertTranslation() { - ClearPendingTranslations(); - - DCHECK(!text_nodes_.empty()); - - std::vector<NodeList*>::iterator text_nodes_iter = text_nodes_.begin(); - std::vector<TextChunks*>::iterator text_chunks_iter = text_chunks_.begin(); - for (;text_nodes_iter != text_nodes_.end(); - ++text_nodes_iter, ++text_chunks_iter) { - DCHECK(text_chunks_iter != text_chunks_.end()); - DCHECK((*text_nodes_iter)->size() == (*text_chunks_iter)->size()); - NodeList::iterator node_iter = (*text_nodes_iter)->begin(); - TextChunks::const_iterator text_iter = (*text_chunks_iter)->begin(); - for (; node_iter != (*text_nodes_iter)->end(); ++node_iter, ++text_iter) { - node_iter->setNodeValue(*text_iter); - } - } - current_language_ = original_language_; -} diff --git a/chrome/renderer/translate/page_translator.h b/chrome/renderer/translate/page_translator.h deleted file mode 100644 index 2b1a9c3..0000000 --- a/chrome/renderer/translate/page_translator.h +++ /dev/null @@ -1,158 +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_RENDERER_TRANSLATE_PAGE_TRANSLATOR_H_ -#define CHROME_RENDERER_TRANSLATE_PAGE_TRANSLATOR_H_ - -#include <map> -#include <set> -#include <stack> -#include <string> -#include <vector> - -#include "base/logging.h" -#include "base/scoped_ptr.h" -#include "base/string16.h" -#include "chrome/common/translate_errors.h" -#include "chrome/renderer/translate/text_translator.h" -#include "third_party/WebKit/WebKit/chromium/public/WebElement.h" - -class RenderView; - -namespace WebKit { -class WebFrame; -class WebNode; -class WebString; -} - -// The PageTranslator is a service that translates the text content of a web -// page from one language to another (ex: English to French). -// It performs the traversal of the DOM of the page to retrieve the text nodes -// and delegates the actual text translation to a TextTranslator. -class PageTranslator : public TextTranslator::Delegate { - public: - // Note that we don't use the simpler name Delegate as it seems to freak-out - // the VisualStudio 2005 compiler in render_view.cc. (RenderView would be - // implementing PageTranslator::Delegate that somehow confuses the compiler - // into thinking we are talkin about TextTranslator::Delegate.) - class PageTranslatorDelegate { - public: - virtual ~PageTranslatorDelegate() {} - virtual void PageTranslated(int page_id, - const std::string& original_lang, - const std::string& target_lang, - TranslateErrors::Type error_type) = 0; - }; - - // The caller remains the owner of |text_translator|. - PageTranslator(TextTranslator* text_translator, - PageTranslatorDelegate* delegate); - virtual ~PageTranslator(); - - // Translate the text in the page contained in |main_frame|. - // It is translated from |source_lang| to |target_lang| where the languages - // are the ISO codes (ex: en, fr...). - // All sub-frames contained in |main_frame| are translated. - void TranslatePage(int page_id, - WebKit::WebFrame* main_frame, - std::string source_lang, - std::string target_lang); - - // Translates the contents of |frame| if it has not already been translated - // and if the main frame was previously translated. - // (Note this should not be called for the main frame, use TranslatePage for - // it). - void TranslateFrame(WebKit::WebFrame* frame); - - // Notification that the main frame of the current page has navigated. - // This invalidates all our page states. - void MainFrameNavigated(); - - // Returns true if the current page has been translated. - bool IsPageTranslated(); - - // TextTranslator::Delegate implentation: - virtual void TranslationError(int work_id, int error_id); - virtual void TextTranslated( - int work_id, const std::vector<string16>& translated_text); - - private: - // Comparator used in set of WebKit WebStrings. - struct WebStringCompare { - bool operator()(const WebKit::WebString& s1, - const WebKit::WebString& s2) const; - }; - - typedef std::vector<WebKit::WebNode> NodeList; - typedef std::vector<string16> TextChunks; - - // Traverses the tree starting at |node| and fills |nodes| with the - // elements necessary for translation. - // |element_stack| is used to retrieve the current node list during the tree - // traversal. - void TraverseNode(WebKit::WebNode node, - std::stack<NodeList*>* element_stack, - std::vector<NodeList*>* nodes); - - // Whether this |element| should be parsed or ignored for translation purpose. - bool ShouldElementBeTraversed(WebKit::WebElement element); - - // Whether this element should be considered as part of the other text nodes - // at the same hiearchical level. - bool IsInlineElement(WebKit::WebElement element); - - // Removes and deletes the NodeZone for |work_id| in pending_translations_. - void ClearNodeZone(int work_id); - - // Clears all the states related to the page's contents. - void ResetPageStates(); - - // Clears any pending translation requests. Any response for a pending - // request received after this call will be ignored. - void ClearPendingTranslations(); - - // Reverts the text nodes in the page to their original text. - void RevertTranslation(); - - // Our delegate (notified when a page is translated). - PageTranslatorDelegate* delegate_; - - // The TextTranslator is responsible for translating the actual text chunks - // from one language to another. - TextTranslator* text_translator_; - - // The list of tags we are not interested in parsing when translating. - std::set<WebKit::WebString, WebStringCompare> ignored_tags_; - - // The list of tags that do not break a block of text. - std::set<WebKit::WebString, WebStringCompare> inline_tags_; - - // Mapping from a translation engine work id to the associated nodes. - std::map<int, NodeList*> pending_translations_; - - // The language the page was in originally. - std::string original_language_; - - // The language the page was translated to. - std::string current_language_; - - // The page id of the page last time we translated. - int page_id_; - - // True if the page is served over HTTPS. - bool secure_page_; - - // The list of text zones in the current page, grouped in text zones (text - // nodes grouped in a same context). - std::vector<NodeList*> text_nodes_; - // The original text of the text nodes in |text_nodes_|. - std::vector<TextChunks*> text_chunks_; - - // A text node containing only the characters in this list is not translated. - string16 ignore_characters_; - - DISALLOW_COPY_AND_ASSIGN(PageTranslator); -}; - -#endif // CHROME_RENDERER_TRANSLATE_PAGE_TRANSLATOR_H_ diff --git a/chrome/renderer/translate/page_translator_unittest.cc b/chrome/renderer/translate/page_translator_unittest.cc deleted file mode 100644 index 02f1b32..0000000 --- a/chrome/renderer/translate/page_translator_unittest.cc +++ /dev/null @@ -1,215 +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/file_path.h" -#include "base/file_util.h" -#include "base/path_service.h" -#include "chrome/renderer/translate/page_translator.h" -#include "chrome/test/render_view_test.h" -#include "net/base/net_errors.h" - -class TranslatorTest : public RenderViewTest { - public: - TranslatorTest() {} -}; - -// A TextTranslator used that simply reverse the strings that are provided to -// it. -class ReverseTextTranslator : public TextTranslator { - public: - ReverseTextTranslator() - : ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)), - work_id_counter_(0) { - } - - virtual int Translate(const std::vector<string16>& text_chunks, - std::string from_lang, - std::string to_lang, - bool secure, - TextTranslator::Delegate* delegate) { - int work_id = work_id_counter_++; - - std::vector<string16> translated_text_chunks; - for (std::vector<string16>::const_iterator iter = text_chunks.begin(); - iter != text_chunks.end(); ++iter) { - translated_text_chunks.push_back(ReverseString(*iter)); - } - MessageLoop::current()->PostTask(FROM_HERE, - method_factory_.NewRunnableMethod( - &ReverseTextTranslator::NotifyDelegate, - work_id, - translated_text_chunks, - delegate)); - return work_id; - } - - private: - void NotifyDelegate(int work_id, - const std::vector<string16>& text_chunks, - TextTranslator::Delegate* delegate) { - delegate->TextTranslated(work_id, text_chunks); - } - - string16 ReverseString(const string16& str) { - string16 result; - for (string16::const_reverse_iterator iter = str.rbegin(); - iter != str.rend(); ++iter) { - result.push_back(*iter); - } - return result; - } - - ScopedRunnableMethodFactory<ReverseTextTranslator> method_factory_; - - int work_id_counter_; - - DISALLOW_COPY_AND_ASSIGN(ReverseTextTranslator); -}; - -// A simple ResourceLoaderBridge that always fails to load. -class DummyResourceLoaderBridge : public webkit_glue::ResourceLoaderBridge { - public: - DummyResourceLoaderBridge() { } - - virtual void AppendDataToUpload(const char* data, int data_len) {} - virtual void AppendFileRangeToUpload( - const FilePath& file_path, - uint64 offset, - uint64 length, - const base::Time& expected_modification_time) {} - virtual void SetUploadIdentifier(int64 identifier) {} - virtual bool Start(Peer* peer) { return false; } - virtual void Cancel() {} - virtual void SetDefersLoading(bool value) {} - virtual void SyncLoad(SyncLoadResponse* response) { - response->status.set_status(URLRequestStatus::FAILED); - response->status.set_os_error(net::ERR_FAILED); - } - - private: - DISALLOW_COPY_AND_ASSIGN(DummyResourceLoaderBridge); -}; - -// A ChildThread class that creates a dummy resource loader bridge so that -// page with resources can be loaded (as data:...) without asserting. -class TestChildThread : public ChildThread { - public: - TestChildThread() {} - - virtual webkit_glue::ResourceLoaderBridge* CreateBridge( - const webkit_glue::ResourceLoaderBridge::RequestInfo& request_info, - int host_renderer_id, - int host_render_view_id) { - return new DummyResourceLoaderBridge; - } - - // Overriden so it does not terminate the message loop. That would assert as - // the message loop is running in the test. - virtual void OnProcessFinalRelease() { } -}; - -// Tests that we parse and change text in pages correctly. -// It loads all the <name>_ORIGINAL.html files under the -// chrome/test/data/translate directory. There must be a matching -// <name>_TRANLSATED.html in the directory. -// The _ORIGINAL page is loaded and translated. -// The result is compared to the _TRANSLATED page. -// Note: _TRANSLATED.html files can be generated using the reverse_text.py -// script located in the chrome/test/data/translate directory. -// TODO(jcampan): http://crbug.com/32217 This test is disabled as it sometimes -// fails on Windows and always fails on Unix. We need to improve -// RenderViewTest so it supports loading tags that contain links -// and sub-resources. -TEST_F(TranslatorTest, DISABLED_TranslatePages) { - // Create the RenderThread singleton. Rendering pages requires a - // VisitedLinkSlave that this object creates. - RenderThread render_thread; - - // Create the ChildThread singleton. It is used to create the resource - // bridges. (It is owned by the ChildProcess.) - ChildProcess::current()->set_main_thread(new TestChildThread()); - - FilePath data_dir; - ASSERT_TRUE(PathService::Get(base::DIR_SOURCE_ROOT, &data_dir)); - data_dir = data_dir.Append(FILE_PATH_LITERAL("chrome")); - data_dir = data_dir.Append(FILE_PATH_LITERAL("test")); - data_dir = data_dir.Append(FILE_PATH_LITERAL("data")); - data_dir = data_dir.Append(FILE_PATH_LITERAL("translate")); - - file_util::FileEnumerator file_enumerator( - data_dir, false, file_util::FileEnumerator::FILES, - FILE_PATH_LITERAL("*_ORIGINAL.html")); - - FilePath original_file_path = file_enumerator.Next(); - while (!original_file_path.empty()) { - // Locate the _TRANSLATED.html file. - FilePath::StringType original_base = original_file_path.BaseName().value(); - LOG(INFO) << "Processing file " << original_base; - size_t orig_index = - original_base.rfind(FILE_PATH_LITERAL("_ORIGINAL.html")); - ASSERT_NE(FilePath::StringType::npos, orig_index); - FilePath::StringType translated_base = original_base.substr(0, orig_index) + - FILE_PATH_LITERAL("_TRANSLATED.html"); - - FilePath translated_file_path(original_file_path.DirName()); - translated_file_path = translated_file_path.Append(translated_base); - - ASSERT_TRUE(file_util::PathExists(translated_file_path)); - - // Load the original file. - int64 size; - ASSERT_TRUE(file_util::GetFileSize(original_file_path, &size)); - scoped_array<char> buffer(new char[static_cast<size_t>(size) + 1]); - ASSERT_EQ(size, file_util::ReadFile(original_file_path, buffer.get(), - static_cast<int>(size))); - buffer[static_cast<size_t>(size)] = '\0'; - LoadHTML(buffer.get()); - - WebKit::WebFrame* web_frame = GetMainFrame(); - ASSERT_TRUE(web_frame); - - // Translate it. - ReverseTextTranslator text_translator; - PageTranslator translator(&text_translator, NULL); - translator.TranslatePage(0, web_frame, "en", "fr"); - - // Translation is asynchronous, so we need to process the pending messages - // to make it happen. - MessageLoop::current()->RunAllPending(); - - WebKit::WebString actual_translated_contents = web_frame->contentAsMarkup(); - - // Load the translated page. - ASSERT_TRUE(file_util::GetFileSize(translated_file_path, &size)); - buffer.reset(new char[static_cast<size_t>(size) + 1]); - ASSERT_EQ(size, file_util::ReadFile(translated_file_path, buffer.get(), - static_cast<int>(size))); - buffer[static_cast<size_t>(size)] = '\0'; - LoadHTML(buffer.get()); - - web_frame = GetMainFrame(); - ASSERT_TRUE(web_frame); - WebKit::WebString expected_translated_contents = - web_frame->contentAsMarkup(); - - EXPECT_EQ(expected_translated_contents.length(), - actual_translated_contents.length()); - - // We compare the actual and expected results by chunks of 80 chars to make - // debugging easier. - int max = std::min(expected_translated_contents.length(), - actual_translated_contents.length()); - int index = 0; - while (index < max) { - int len = std::min(80, max - index); - string16 expected(expected_translated_contents.data() + index, len); - string16 actual(actual_translated_contents.data() + index, len); - ASSERT_EQ(expected, actual); - index += 80; - } - - // Iterate to the next file to test. - original_file_path = file_enumerator.Next(); - } -} diff --git a/chrome/renderer/translate/text_translator.h b/chrome/renderer/translate/text_translator.h deleted file mode 100644 index b4e7581..0000000 --- a/chrome/renderer/translate/text_translator.h +++ /dev/null @@ -1,49 +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_RENDERER_TRANSLATE_TEXT_TRANSLATOR_H_ -#define CHROME_RENDERER_TRANSLATE_TEXT_TRANSLATOR_H_ - -#include <string> -#include <vector> - -#include "base/string16.h" - -// TextTranslator is an interface that is implemented by providers that know -// how to translate text from one language to another. -// It is asynchronous. Clients call Translate() with the text to translate and -// receive a work id. The implementation should call the TextTranslated -// method on the delegate once the text has been translated. - -class TextTranslator { - public: - class Delegate { - public: - virtual ~Delegate() {} - - // Notifies that the translation failed for |work_id|. - virtual void TranslationError(int work_id, int error_id) = 0; - - // Notifies that the translation for |work_id| succeeded. - virtual void TextTranslated( - int work_id, const std::vector<string16>& translated_text) = 0; - }; - - TextTranslator() {} - virtual ~TextTranslator() {} - - // Initiates the translation of the |text| provided, from the language - // |from_lang| to |to_lang| (these are the ISO language code, for example en, - // fr, ja...). If |secure| is true then a secure communication method (HTTPS) - // should be used if using a remote resource to perform the translation. - // Returns a work id that is passed as a parameter when delegate methods are - // called on |delegate| to notify the translation succeeded/failed. - virtual int Translate(const std::vector<string16>& text, - std::string from_lang, - std::string to_lang, - bool secure, - TextTranslator::Delegate* delegate) = 0; -}; - -#endif // CHROME_RENDERER_TRANSLATE_TEXT_TRANSLATOR_H_ diff --git a/chrome/renderer/translate/text_translator_impl.cc b/chrome/renderer/translate/text_translator_impl.cc deleted file mode 100644 index d2b668c..0000000 --- a/chrome/renderer/translate/text_translator_impl.cc +++ /dev/null @@ -1,41 +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/renderer/translate/text_translator_impl.h" - -#include "chrome/common/render_messages.h" -#include "chrome/renderer/render_view.h" - -TextTranslatorImpl::TextTranslatorImpl(RenderView* render_view) - : render_view_(render_view), - work_id_counter_(0) { -} - -void TextTranslatorImpl::OnTranslationResponse( - int work_id, int error_id, const std::vector<string16>& text_chunks) { - if (error_id) { - render_view_->page_translator()->TranslationError(work_id, error_id); - return; - } - render_view_->page_translator()->TextTranslated(work_id, text_chunks); -} - -int TextTranslatorImpl::Translate(const std::vector<string16>& text, - std::string from_lang, - std::string to_lang, - bool secure, - TextTranslator::Delegate* delegate) { - ViewHostMsg_TranslateTextParam param; - param.routing_id = render_view_->routing_id(); - param.page_id = render_view_->page_id(); - param.work_id = work_id_counter_++; - param.from_language = from_lang; - param.to_language = to_lang; - param.text_chunks = text; - param.secure = secure; - - render_view_->Send(new ViewHostMsg_TranslateText(param)); - - return param.work_id; -} diff --git a/chrome/renderer/translate/text_translator_impl.h b/chrome/renderer/translate/text_translator_impl.h deleted file mode 100644 index 566a084..0000000 --- a/chrome/renderer/translate/text_translator_impl.h +++ /dev/null @@ -1,51 +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_RENDERER_TRANSLATE_TEXT_TRANSLATOR_IMPL_H_ -#define CHROME_RENDERER_TRANSLATE_TEXT_TRANSLATOR_IMPL_H_ - -#include <string> -#include <vector> - -#include "base/logging.h" -#include "chrome/renderer/translate/text_translator.h" - -class RenderView; - -// An implementation of the TextTranslator that sends translation requests -// to the browser. -// There is one instance of TextTranslatorImpl per RenderViewHost and the -// RenderViewHost owns that instance. -// -// TODO(jcampan): limit the number of translation requests in flight so not to -// swamp the browser's resource dispatcher host. - -class TextTranslatorImpl : public TextTranslator { - public: - explicit TextTranslatorImpl(RenderView* render_view); - - // Called by the renderer to notify a translation response has been received. - // |error_id| is different than 0 if an error occurred. - void OnTranslationResponse(int work_id, - int error_id, - const std::vector<string16>& text_chunks); - - // TextTranslator implementation. - virtual int Translate(const std::vector<string16>& text, - std::string from_lang, - std::string to_lang, - bool secure, - TextTranslator::Delegate* delegate); - private: - // The render view through which translation requests/responses are - // sent/received. - RenderView* render_view_; - - // The counter used to create work ids. - int work_id_counter_; - - DISALLOW_COPY_AND_ASSIGN(TextTranslatorImpl); -}; - -#endif // CHROME_RENDERER_TRANSLATE_TEXT_TRANSLATOR_IMPL_H_ diff --git a/chrome/renderer/translate_helper.cc b/chrome/renderer/translate_helper.cc new file mode 100644 index 0000000..f2a3f66 --- /dev/null +++ b/chrome/renderer/translate_helper.cc @@ -0,0 +1,230 @@ +// 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/renderer/translate_helper.h" + +#include "base/compiler_specific.h" +#include "chrome/renderer/render_view.h" +#include "third_party/WebKit/WebKit/chromium/public/WebFrame.h" +#include "third_party/WebKit/WebKit/chromium/public/WebScriptSource.h" + +using WebKit::WebFrame; +using WebKit::WebScriptSource; + +// The delay in millliseconds that we'll wait before checking to see if the +// translate library injected in the page is ready. +static const int kTranslateInitCheckDelayMs = 150; + +// The maximum number of times we'll check to see if the translate library +// injected in the page is ready. +static const int kMaxTranslateInitCheckAttempts = 5; + +// The delay we wait in milliseconds before checking whether the translation has +// finished. +static const int kTranslateStatusCheckDelayMs = 400; + +//////////////////////////////////////////////////////////////////////////////// +// TranslateHelper, public: +// +TranslateHelper::TranslateHelper(RenderView* render_view) + : render_view_(render_view), + ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)) { +} + +void TranslateHelper::TranslatePage(int page_id, + const std::string& source_lang, + const std::string& target_lang, + const std::string& translate_script) { + if (render_view_->page_id() != page_id) + return; // We navigated away, nothing to do. + + if (!IsTranslateLibAvailable()) { + // Evaluate the script to add the translation related method to the global + // context of the page. + ExecuteScript(translate_script); + DCHECK(IsTranslateLibAvailable()); + } + + // Cancel any pending tasks related to a previous translation, they are now + // obsolete. + method_factory_.RevokeAll(); + + TranslatePageImpl(page_id, source_lang, target_lang, 0); +} + +void TranslateHelper::RevertTranslation(int page_id) { + if (render_view_->page_id() != page_id) + return; // We navigated away, nothing to do. + + if (!IsTranslateLibAvailable()) { + NOTREACHED(); + return; + } + + WebFrame* main_frame = render_view_->webview()->mainFrame(); + if (!main_frame) + return; + + main_frame->executeScript( + WebScriptSource(ASCIIToUTF16("cr.googleTranslate.revert()"))); +} + +//////////////////////////////////////////////////////////////////////////////// +// TranslateHelper, protected: +// +bool TranslateHelper::IsTranslateLibAvailable() { + bool lib_available = false; + if (!ExecuteScriptAndGetBoolResult( + "typeof cr != 'undefined' && typeof cr.googleTranslate != 'undefined' && " + "typeof cr.googleTranslate.translate == 'function'", &lib_available)) { + NOTREACHED(); + return false; + } + return lib_available; +} + +bool TranslateHelper::IsTranslateLibReady() { + bool lib_ready = false; + if (!ExecuteScriptAndGetBoolResult("cr.googleTranslate.libReady", + &lib_ready)) { + NOTREACHED(); + return false; + } + return lib_ready; +} + +bool TranslateHelper::HasTranslationFinished() { + bool translation_finished = false; + if (!ExecuteScriptAndGetBoolResult("cr.googleTranslate.finished", + &translation_finished)) { + NOTREACHED() << "crGoogleTranslateGetFinished returned unexpected value."; + return true; + } + + return translation_finished; +} + +bool TranslateHelper::HasTranslationFailed() { + bool translation_failed = false; + if (!ExecuteScriptAndGetBoolResult("cr.googleTranslate.error", + &translation_failed)) { + NOTREACHED() << "crGoogleTranslateGetError returned unexpected value."; + return true; + } + + return translation_failed; +} + +bool TranslateHelper::StartTranslation(const std::string& source_lang, + const std::string& target_lang) { + bool translate_success = false; + if (!ExecuteScriptAndGetBoolResult("cr.googleTranslate.translate('" + + source_lang + "','" + target_lang + "')", + &translate_success)) { + NOTREACHED(); + return false; + } + return translate_success; +} + +//////////////////////////////////////////////////////////////////////////////// +// TranslateHelper, private: +// +void TranslateHelper::CheckTranslateStatus(int page_id, + const std::string& source_lang, + const std::string& target_lang) { + if (page_id != render_view_->page_id()) + return; // This is not the same page, the translation has been canceled. + + // First check if there was an error. + if (HasTranslationFailed()) { + NotifyBrowserTranslationFailed(source_lang, target_lang, + TranslateErrors::TRANSLATION_ERROR); + return; // There was an error. + } + + if (HasTranslationFinished()) { + // Translation was successfull, notify the browser. + render_view_->Send(new ViewHostMsg_PageTranslated( + render_view_->routing_id(), render_view_->page_id(), + source_lang, target_lang, TranslateErrors::NONE)); + return; + } + + // The translation is still pending, check again later. + MessageLoop::current()->PostDelayedTask(FROM_HERE, + method_factory_.NewRunnableMethod(&TranslateHelper::CheckTranslateStatus, + page_id, source_lang, target_lang), + DontDelayTasks() ? 0 : kTranslateStatusCheckDelayMs); +} + +bool TranslateHelper::ExecuteScript(const std::string& script) { + WebFrame* main_frame = render_view_->webview()->mainFrame(); + if (!main_frame) + return false; + main_frame->executeScript(WebScriptSource(ASCIIToUTF16(script))); + return true; +} + +bool TranslateHelper::ExecuteScriptAndGetBoolResult(const std::string& script, + bool* value) { + DCHECK(value); + WebFrame* main_frame = render_view_->webview()->mainFrame(); + if (!main_frame) + return false; + + v8::Handle<v8::Value> v = main_frame->executeScriptAndReturnValue( + WebScriptSource(ASCIIToUTF16(script))); + if (v.IsEmpty() || !v->IsBoolean()) + return false; + + *value = v->BooleanValue(); + return true; +} + +void TranslateHelper::TranslatePageImpl(int page_id, + const std::string& source_lang, + const std::string& target_lang, + int count) { + DCHECK_LT(count, kMaxTranslateInitCheckAttempts); + if (page_id != render_view_->page_id()) + return; + + if (!IsTranslateLibReady()) { + // The library is not ready, try again later, unless we have tried several + // times unsucessfully already. + if (++count >= kMaxTranslateInitCheckAttempts) { + NotifyBrowserTranslationFailed(source_lang, target_lang, + TranslateErrors::INITIALIZATION_ERROR); + return; + } + MessageLoop::current()->PostDelayedTask(FROM_HERE, + method_factory_.NewRunnableMethod(&TranslateHelper::TranslatePageImpl, + page_id, source_lang, target_lang, + count), + DontDelayTasks() ? 0 : count * kTranslateInitCheckDelayMs); + return; + } + + if (!StartTranslation(source_lang, target_lang)) { + NotifyBrowserTranslationFailed(source_lang, target_lang, + TranslateErrors::TRANSLATION_ERROR); + return; + } + // Check the status of the translation. + MessageLoop::current()->PostDelayedTask(FROM_HERE, + method_factory_.NewRunnableMethod(&TranslateHelper::CheckTranslateStatus, + page_id, source_lang, target_lang), + DontDelayTasks() ? 0 : kTranslateStatusCheckDelayMs); +} + +void TranslateHelper::NotifyBrowserTranslationFailed( + const std::string& source_lang, + const std::string& target_lang, + TranslateErrors::Type error) { + // Notify the browser there was an error. + render_view_->Send(new ViewHostMsg_PageTranslated( + render_view_->routing_id(), render_view_->page_id(), + source_lang, target_lang, error)); +} diff --git a/chrome/renderer/translate_helper.h b/chrome/renderer/translate_helper.h new file mode 100644 index 0000000..efd5a72 --- /dev/null +++ b/chrome/renderer/translate_helper.h @@ -0,0 +1,103 @@ +// 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_RENDERER_TRANSLATE_HELPER_H_ +#define CHROME_RENDERER_TRANSLATE_HELPER_H_ + +#include <string> + +#include "base/task.h" +#include "chrome/common/translate_errors.h" + +class RenderView; + +// This class deals with page translation. +// There is one TranslateHelper per RenderView. + +class TranslateHelper { + public: + explicit TranslateHelper(RenderView* render_view); + virtual ~TranslateHelper() {} + + // Translates the page contents from |source_lang| to |target_lang|. + // Does nothing if |page_id| is not the current page id. + // If the library is not ready, it will post a task to try again after 50ms. + void TranslatePage(int page_id, + const std::string& source_lang, + const std::string& target_lang, + const std::string& translate_script); + + // Reverts the page's text to its original contents. + void RevertTranslation(int page_id); + + protected: + // The following methods are protected so they can be overridden in + // unit-tests. + + // Returns true if the translate library is available, meaning the JavaScript + // has already been injected in that page. + virtual bool IsTranslateLibAvailable(); + + // Returns true if the translate library has been initialized successfully. + virtual bool IsTranslateLibReady(); + + // Returns true if the translation script has finished translating the page. + virtual bool HasTranslationFinished(); + + // Returns true if the translation script has reported an error performing the + // translation. + virtual bool HasTranslationFailed(); + + // Starts the translation by calling the translate library. This method + // should only be called when the translate script has been injected in the + // page. Returns false if the call failed immediately. + virtual bool StartTranslation(const std::string& original_lang, + const std::string& target_lang); + + // Used in unit-tests. Makes the various tasks be posted immediately so that + // the tests don't have to wait before checking states. + virtual bool DontDelayTasks() { return false; } + + private: + // Checks if the current running page translation is finished or errored and + // notifies the browser accordingly. If the translation has not terminated, + // posts a task to check again later. + void CheckTranslateStatus(int page_id, + const std::string& source_lang, + const std::string& target_lang); + + // Executes the JavaScript code in |script| in the main frame of + // |render_view_host_|. + // Returns true if the code was executed successfully. + bool ExecuteScript(const std::string& script); + + // Executes the JavaScript code in |script| in the main frame of + // |render_view_host_|, and sets |value| to the boolean returned by the script + // evaluation. Returns true if the script was run successfully and returned + // a boolean, false otherwise + bool ExecuteScriptAndGetBoolResult(const std::string& script, bool* value); + + // Called by TranslatePage to do the actual translation. |count| is used to + // limit the number of retries. + void TranslatePageImpl(int page_id, + const std::string& source_lang, + const std::string& target_lang, + int count); + + // Sends a message to the browser to notify it that the translation failed + // with |error|. + void NotifyBrowserTranslationFailed(const std::string& original_lang, + const std::string& target_lang, + TranslateErrors::Type error); + + // The RenderView we are performing translations for. + RenderView* render_view_; + + // Method factory used to make calls to TranslatePageImpl. + ScopedRunnableMethodFactory<TranslateHelper> method_factory_; + + DISALLOW_COPY_AND_ASSIGN(TranslateHelper); +}; + +#endif // CHROME_RENDERER_TRANSLATE_HELPER_H_ diff --git a/chrome/renderer/translate_helper_unittest.cc b/chrome/renderer/translate_helper_unittest.cc new file mode 100644 index 0000000..c0f0e5e --- /dev/null +++ b/chrome/renderer/translate_helper_unittest.cc @@ -0,0 +1,169 @@ +// 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/renderer/translate_helper.h" +#include "chrome/test/render_view_test.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using testing::AtLeast; +using testing::Return; + +class TestTranslateHelper : public TranslateHelper { + public: + explicit TestTranslateHelper(RenderView* render_view) + : TranslateHelper(render_view) { + } + + virtual bool DontDelayTasks() { return true; } + + MOCK_METHOD0(IsTranslateLibAvailable, bool()); + MOCK_METHOD0(IsTranslateLibReady, bool()); + MOCK_METHOD0(HasTranslationFinished, bool()); + MOCK_METHOD0(HasTranslationFailed, bool()); + MOCK_METHOD2(StartTranslation, bool(const std::string& source_lang, + const std::string& target_lang)); + + private: + DISALLOW_COPY_AND_ASSIGN(TestTranslateHelper); +}; + +class TranslateHelperTest : public RenderViewTest { + public: + TranslateHelperTest() {} + + protected: + virtual void SetUp() { + RenderViewTest::SetUp(); + translate_helper_.reset(new TestTranslateHelper(view_)); + } + + bool GetPageTranslatedMessage(int* page_id, + std::string* original_lang, + std::string* target_lang, + TranslateErrors::Type* error) { + const IPC::Message* message = render_thread_.sink(). + GetUniqueMessageMatching(ViewHostMsg_PageTranslated::ID); + if (!message) + return false; + Tuple4<int, std::string, std::string, TranslateErrors::Type> + translate_param; + ViewHostMsg_PageTranslated::Read(message, &translate_param); + if (page_id) + *page_id = translate_param.a; + if (original_lang) + *original_lang = translate_param.b; + if (target_lang) + *target_lang = translate_param.c; + if (error) + *error = translate_param.d; + return true; + } + + scoped_ptr<TestTranslateHelper> translate_helper_; +}; + +// Tests that the browser gets notified of the translation failure if the +// translate library fails/times-out during initialization. +TEST_F(TranslateHelperTest, TranslateLibNeverReady) { + // We make IsTranslateLibAvailable true so we don't attempt to inject the + // library. + EXPECT_CALL(*translate_helper_, IsTranslateLibAvailable()) + .Times(AtLeast(1)) + .WillRepeatedly(Return(true)); + + EXPECT_CALL(*translate_helper_, IsTranslateLibReady()) + .Times(AtLeast(5)) // See kMaxTranslateInitCheckAttempts in + // translate_helper.cc + .WillRepeatedly(Return(false)); + + translate_helper_->TranslatePage(view_->page_id(), "en", "fr", std::string()); + MessageLoop::current()->RunAllPending(); + + int page_id; + TranslateErrors::Type error; + ASSERT_TRUE(GetPageTranslatedMessage(&page_id, NULL, NULL, &error)); + EXPECT_EQ(view_->page_id(), page_id); + EXPECT_EQ(TranslateErrors::INITIALIZATION_ERROR, error); +} + +// Tests that the browser gets notified of the translation success when the +// translation succeeds. +TEST_F(TranslateHelperTest, TranslateSuccess) { + // We make IsTranslateLibAvailable true so we don't attempt to inject the + // library. + EXPECT_CALL(*translate_helper_, IsTranslateLibAvailable()) + .Times(AtLeast(1)) + .WillRepeatedly(Return(true)); + + EXPECT_CALL(*translate_helper_, IsTranslateLibReady()) + .WillOnce(Return(false)) + .WillOnce(Return(true)); + + EXPECT_CALL(*translate_helper_, StartTranslation("en", "fr")) + .WillOnce(Return(true)); + + // Succeed after few checks. + EXPECT_CALL(*translate_helper_, HasTranslationFailed()) + .WillRepeatedly(Return(false)); + EXPECT_CALL(*translate_helper_, HasTranslationFinished()) + .WillOnce(Return(false)) + .WillOnce(Return(false)) + .WillOnce(Return(true)); + + std::string original_lang("en"); + std::string target_lang("fr"); + translate_helper_->TranslatePage(view_->page_id(), original_lang, target_lang, + std::string()); + MessageLoop::current()->RunAllPending(); + + int page_id; + std::string received_original_lang; + std::string received_target_lang; + TranslateErrors::Type error; + ASSERT_TRUE(GetPageTranslatedMessage(&page_id, + &received_original_lang, + &received_target_lang, + &error)); + EXPECT_EQ(view_->page_id(), page_id); + EXPECT_EQ(original_lang, received_original_lang); + EXPECT_EQ(target_lang, received_target_lang); + EXPECT_EQ(TranslateErrors::NONE, error); +} + +// Tests that the browser gets notified of the translation failure when the +// translation fails. +TEST_F(TranslateHelperTest, TranslateFailure) { + // We make IsTranslateLibAvailable true so we don't attempt to inject the + // library. + EXPECT_CALL(*translate_helper_, IsTranslateLibAvailable()) + .Times(AtLeast(1)) + .WillRepeatedly(Return(true)); + + EXPECT_CALL(*translate_helper_, IsTranslateLibReady()) + .WillOnce(Return(true)); + + EXPECT_CALL(*translate_helper_, StartTranslation("en", "fr")) + .WillOnce(Return(true)); + + // Fail after few checks. + EXPECT_CALL(*translate_helper_, HasTranslationFailed()) + .WillOnce(Return(false)) + .WillOnce(Return(false)) + .WillOnce(Return(false)) + .WillOnce(Return(true)); + + EXPECT_CALL(*translate_helper_, HasTranslationFinished()) + .Times(AtLeast(1)) + .WillRepeatedly(Return(false)); + + translate_helper_->TranslatePage(view_->page_id(), "en", "fr", std::string()); + MessageLoop::current()->RunAllPending(); + + int page_id; + TranslateErrors::Type error; + ASSERT_TRUE(GetPageTranslatedMessage(&page_id, NULL, NULL, &error)); + EXPECT_EQ(view_->page_id(), page_id); + EXPECT_EQ(TranslateErrors::TRANSLATION_ERROR, error); +} diff --git a/chrome/test/data/translate/basic_ORIGINAL.html b/chrome/test/data/translate/basic_ORIGINAL.html deleted file mode 100644 index 420ca73..0000000 --- a/chrome/test/data/translate/basic_ORIGINAL.html +++ /dev/null @@ -1,32 +0,0 @@ -<html> - -<script> -function buttonClicked() { - div = document.getElementById("lastDiv"); - bold = document.getElementById("bold"); - - text_node = document.createTextNode("Hello!"); - // div.childNodes[0].appendChild(text_node); - p_node = document.createElement("p"); - p_node.appendChild(text_node); - bold.appendChild(p_node); -} -</script> - -<body> - - <p>A simple paragraph. Nothing to see here, move along!</p> - - <p>This is a paragraph with a <a href="">link</a> and some <b id="bold">bold text</b> in it!</p> - <p>This on uses a <SPAN>span</SPAN> </p> - - - <dIV> - <div>This is a first div <div>with an inner div</div> and that's it</div> - <div id="lastDiv">OK, last div 0.69%</div> - </div> - - <button onclick="buttonClicked()">Click me </button> -</body> - -</html> diff --git a/chrome/test/data/translate/basic_TRANSLATED.html b/chrome/test/data/translate/basic_TRANSLATED.html deleted file mode 100644 index b95f28b..0000000 --- a/chrome/test/data/translate/basic_TRANSLATED.html +++ /dev/null @@ -1,33 +0,0 @@ -<html> - -<script> -function buttonClicked() { - div = document.getElementById("lastDiv"); - bold = document.getElementById("bold"); - - text_node = document.createTextNode("Hello!"); - // div.childNodes[0].appendChild(text_node); - p_node = document.createElement("p"); - p_node.appendChild(text_node); - bold.appendChild(p_node); -} -</script> - -<body> - - <p>!gnola evom ,ereh ees ot gnihtoN .hpargarap elpmis A</p> - - <p> a htiw hpargarap a si sihT<a href="">knil</a> emos dna <b id="bold">txet dlob</b>!ti ni </p> - <p> a sesu no sihT<SPAN>naps</SPAN> </p> - - - <dIV> - <div> vid tsrif a si sihT<div>vid renni na htiw</div>ti s'taht dna </div> - <div id="lastDiv">%96.0 vid tsal ,KO</div> - </div> - - <button onclick="buttonClicked()"> em kcilC</button> -</body> - -</html> - diff --git a/chrome/test/data/translate/reverse_text.py b/chrome/test/data/translate/reverse_text.py deleted file mode 100644 index baed9d1..0000000 --- a/chrome/test/data/translate/reverse_text.py +++ /dev/null @@ -1,223 +0,0 @@ -#!/usr/bin/env python -# Copyright (c) 2009 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. - -""" Reverses the text of an HTML file. - -This classes poorly parses an HTML file and reverse the text strings (and only -the text, not the tags). -It is used to generates the _TRANSLATED.html files that the translator unittest -uses. -Note it is very hacky and buggy. -""" - -import codecs -import re -import sys - -def Error(msg): - print msg; - sys.exit(1); - -class HTMLParser: - # IGNORED_[PAIRED|SINGLE]_TAGS should be kept in sync with kSkippedTags (see - # chrome/renderer/translator.cc). - # Paired tags are tags that are expected to have an opening and closing tag, - # the entire zone they contain is ignored. - # Single tags are not closed and are ignored. - IGNORED_PAIRED_TAGS = [ "APPLET", "AREA", "BASE", "FRAME", "FRAMESET", "HR", - "IFRAME", "MAP", "OBJECT", "PARAM", "SCRIPT", "STYLE", - "TEXTAREA" ]; - IGNORED_SINGLE_TAGS = [ "META", "LINK", "IMG", "INPUT" ]; - - def __init__(self, input_path, output_path): - try: - input_file = codecs.open(input_path, 'r', 'utf-8'); - except IOError: - Error("Failed to open '" + input_path + "' for reading."); - - self.html_contents = input_file.read(); - # Python does not have a find method case-insensitive, so we keep a lower - # case copy of the contents. - self.html_contents_lower = self.html_contents.lower(); - - input_file.close(); - - self.read_index = 0 - self.write_index = 0 - try: - self.output_file = codecs.open(output_path, 'w', 'utf-8'); - except IOError: - Error("Failed to open '" + output_path + "' for writting."); - - def printDebug(self, msg): - print u"** %s" % msg.encode('ascii', 'replace') - - def removeBlanks(self, str): - p = re.compile('\s'); - return p.sub('', str); - - def extractTagName(self, str): - closing_tag = False; - str = str.strip(); - if str[0] != "<": - Error("Interal error: attempting to extract tag name from invalid tag: " + - str); - if str[1] == "/": - closing_tag = True; - - p = re.compile('</?\s*(\w*).*'); - m = p.match(str); - if m == None: - Error("Interal error: failed to extract tag name from tag: " + str); - return (m.group(1).lower(), closing_tag); - - def shouldIgnoreTag(self, tag): - """Returns a tuple (tag should be ignored, pared tags) - """ - tag = tag.upper(); - for tag_to_ignore in self.IGNORED_PAIRED_TAGS: - if tag_to_ignore == tag: - return True, True; - for tag_to_ignore in self.IGNORED_SINGLE_TAGS: - if tag_to_ignore == tag: - return True, False; - return False, False; - - def skipToEndTag(self, tag): - """ Move the read_index to the position after the closing tag matching - |tag| and copies all the skipped data to the output file.""" - index = self.html_contents_lower.find("</" + tag, self.read_index); - if index == -1: - Error("Failed to find tag end for tag " + tag + " at index " + - str(self.read_index)); - self.writeToOutputFile(self.html_contents[self.read_index:]); - else: - self.writeToOutputFile(self.html_contents[self.read_index:index]); - self.read_index = index; - - def writeToOutputFile(self, text): - try: - self.output_file.write(text) - except IOError: - Error("Failed to write to output file."); - # DEBUG - if len(text) > 100000: - Error("Writting too much text: " + text); -# self.printDebug("Writting: " + text); -# self.write_index += len(text); -# self.printDebug("Wrote " + str(len(text)) + " bytes, write len=" + str(self.write_index)); - - def getNextTag(self): - """Moves the read_index to the end of the next tag and writes the tag to the - output file. - Returns a tuple end of file reached, tag name, if closing tag. - """ - - start_index = self.html_contents.find("<", self.read_index); - if start_index == -1: - self.writeToOutputFile(self.html_contents[self.read_index:]); - return (True, "", False); - stop_index = self.html_contents.find(">", start_index); - if stop_index == -1: - print "Unclosed tag found."; - self.writeToOutputFile(self.html_contents[self.read_index:]); - return (True, "", False); - - # Write to the file the current text reverted. - # No need to do it if the string is only blanks, that would break the - # indentation. - text = self.html_contents[self.read_index:start_index] - text = self.processText(text); - self.writeToOutputFile(text); - - tag = self.html_contents[start_index:stop_index + 1]; - self.writeToOutputFile(tag); - self.read_index = stop_index + 1; - tag_name, closing_tag = self.extractTagName(tag); -# self.printDebug("Raw tag=" + tag); -# self.printDebug("tag=" + tag_name + " closing=" + str(closing_tag)); -# self.printDebug("read_index=" + str(self.read_index)); - - return (False, tag_name, closing_tag); - - def processText(self, text): - if text.isspace(): - return text; - - # Special case of lonely with spaces. It should not be reversed as - # the renderer does not "translate" it as it is seen as empty string. - if text.strip().lower() == ' ': - return text; - - # We reverse the string manually so to preserve and friends. - p = re.compile(r'&#\d{1,5};|&\w{2,6};'); - # We create a dictionary where the key is the index at which the ASCII code - # starts and the value the index at which it ends. - entityNameIndexes = dict(); - for match in p.finditer(text): - entityNameIndexes[match.start()] = match.end(); - result = "" - i = 0; - while i < len(text): - if entityNameIndexes.has_key(i): - end_index = entityNameIndexes[i]; - result = text[i:end_index] + result; - i = end_index; - elif text[i] == "%": # Replace percent to avoid percent encoding. - result = "%" + result; - i = i + 1; - else: - result = text[i] + result; - i = i + 1; - - return result; - - def processTagContent(self): - """Reads the text from the current index to the next tag and writes the text - in reverse to the output file. - """ - stop_index = self.html_contents.find("<", self.read_index); - if stop_index == -1: - text = self.html_contents[self.read_index:]; - self.read_index += len(text); - else: - text = self.html_contents[self.read_index:stop_index]; - self.read_index = stop_index; - text = self.processText(text); - self.writeToOutputFile(text); - - def start(self): - while True: - end_of_file, tag, closing_tag = self.getNextTag(); - # if closing_tag: - # self.printDebug("Read tag: /" + tag); - # else: - # self.printDebug("Read tag: " + tag); - - if end_of_file: # We reached the end of the file. - self.writeToOutputFile(self.html_contents[self.read_index:]); - print "Done."; - sys.exit(0); - - if closing_tag: - continue; - - ignore_tag, paired_tag = self.shouldIgnoreTag(tag); - if ignore_tag and paired_tag: - self.skipToEndTag(tag); - - # Read and reverse the text in the tab. - self.processTagContent(); - -def main(): - if len(sys.argv) != 3: - Error("Reverse the text in HTML pages\n" - "Usage reversetext.py <original_file.html> <dest_file.html>"); - - html_parser = HTMLParser(sys.argv[1], sys.argv[2]); - html_parser.start(); - -if __name__ == "__main__": - main() |