diff options
Diffstat (limited to 'chrome/browser')
-rw-r--r-- | chrome/browser/net/test_url_fetcher_factory.h | 4 | ||||
-rw-r--r-- | chrome/browser/net/url_fetcher.cc | 4 | ||||
-rw-r--r-- | chrome/browser/net/url_fetcher.h | 5 | ||||
-rw-r--r-- | chrome/browser/renderer_host/render_process_host.h | 2 | ||||
-rw-r--r-- | chrome/browser/renderer_host/render_view_host.cc | 7 | ||||
-rw-r--r-- | chrome/browser/renderer_host/render_view_host.h | 6 | ||||
-rw-r--r-- | chrome/browser/renderer_host/resource_message_filter.cc | 2 | ||||
-rw-r--r-- | chrome/browser/renderer_host/translation_service.cc | 548 | ||||
-rw-r--r-- | chrome/browser/renderer_host/translation_service.h | 140 | ||||
-rw-r--r-- | chrome/browser/renderer_host/translation_service_unittest.cc | 458 | ||||
-rw-r--r-- | chrome/browser/tab_contents/tab_contents.cc | 14 |
11 files changed, 1165 insertions, 25 deletions
diff --git a/chrome/browser/net/test_url_fetcher_factory.h b/chrome/browser/net/test_url_fetcher_factory.h index 12864a0..a6ea7c4 100644 --- a/chrome/browser/net/test_url_fetcher_factory.h +++ b/chrome/browser/net/test_url_fetcher_factory.h @@ -6,6 +6,7 @@ #define CHROME_BROWSER_NET_TEST_URL_FETCHER_FACTORY_H_ #include <map> +#include <string> #include "chrome/browser/net/url_fetcher.h" #include "googleurl/src/gurl.h" @@ -48,6 +49,9 @@ class TestURLFetcher : public URLFetcher { // in your tests. const GURL& original_url() const { return original_url_; } + // Returns the data uploaded on this URLFetcher. + const std::string& upload_data() const { return URLFetcher::upload_data(); } + private: const GURL original_url_; diff --git a/chrome/browser/net/url_fetcher.cc b/chrome/browser/net/url_fetcher.cc index 14539a2..a0a96eb 100644 --- a/chrome/browser/net/url_fetcher.cc +++ b/chrome/browser/net/url_fetcher.cc @@ -303,6 +303,10 @@ void URLFetcher::set_upload_data(const std::string& upload_content_type, core_->upload_content_ = upload_content; } +const std::string& URLFetcher::upload_data() const { + return core_->upload_content_; +} + void URLFetcher::set_load_flags(int load_flags) { core_->load_flags_ = load_flags; } diff --git a/chrome/browser/net/url_fetcher.h b/chrome/browser/net/url_fetcher.h index 2d581f4..35fc37d 100644 --- a/chrome/browser/net/url_fetcher.h +++ b/chrome/browser/net/url_fetcher.h @@ -10,6 +10,8 @@ #ifndef CHROME_BROWSER_NET_URL_FETCHER_H_ #define CHROME_BROWSER_NET_URL_FETCHER_H_ +#include <string> + #include "base/leak_tracker.h" #include "base/message_loop.h" #include "base/ref_counted.h" @@ -148,6 +150,9 @@ class URLFetcher { // Returns the delegate. Delegate* delegate() const; + // Used by tests. + const std::string& upload_data() const; + private: class Core; diff --git a/chrome/browser/renderer_host/render_process_host.h b/chrome/browser/renderer_host/render_process_host.h index 9d4855c..9835776 100644 --- a/chrome/browser/renderer_host/render_process_host.h +++ b/chrome/browser/renderer_host/render_process_host.h @@ -273,7 +273,7 @@ class RenderProcessHost : public IPC::Channel::Sender, bool fast_shutdown_started_; private: - // The globally-uniqe identifier for this RPH. + // The globally-unique identifier for this RPH. int id_; Profile* profile_; diff --git a/chrome/browser/renderer_host/render_view_host.cc b/chrome/browser/renderer_host/render_view_host.cc index a227ffa..652d253 100644 --- a/chrome/browser/renderer_host/render_view_host.cc +++ b/chrome/browser/renderer_host/render_view_host.cc @@ -1751,6 +1751,13 @@ void RenderViewHost::PerformCustomContextMenuAction(unsigned action) { Send(new ViewMsg_CustomContextMenuAction(routing_id(), action)); } +void RenderViewHost::TranslatePage(int page_id, + const std::string& source_lang, + const std::string& target_lang) { + Send(new ViewMsg_TranslatePage(routing_id(), page_id, + source_lang, target_lang)); +} + void RenderViewHost::OnExtensionPostMessage( int port_id, const std::string& message) { if (process()->profile()->GetExtensionMessageService()) { diff --git a/chrome/browser/renderer_host/render_view_host.h b/chrome/browser/renderer_host/render_view_host.h index 4a3966d..0df557d 100644 --- a/chrome/browser/renderer_host/render_view_host.h +++ b/chrome/browser/renderer_host/render_view_host.h @@ -441,6 +441,12 @@ class RenderViewHost : public RenderWidgetHost { // Tells the render view that a custom context action has been selected. void PerformCustomContextMenuAction(unsigned action); + // 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. + void TranslatePage(int page_id, + const std::string& source_lang, + const std::string& target_lang); + protected: // RenderWidgetHost protected overrides. virtual bool PreHandleKeyboardEvent(const NativeWebKeyboardEvent& event, diff --git a/chrome/browser/renderer_host/resource_message_filter.cc b/chrome/browser/renderer_host/resource_message_filter.cc index a83bdba..516979a 100644 --- a/chrome/browser/renderer_host/resource_message_filter.cc +++ b/chrome/browser/renderer_host/resource_message_filter.cc @@ -1211,7 +1211,7 @@ void ResourceMessageFilter::OnKeygen(uint32 key_size_index, void ResourceMessageFilter::OnTranslateText( ViewHostMsg_TranslateTextParam param) { - translation_service_.Translate(param.routing_id, param.work_id, + translation_service_.Translate(param.routing_id, param.page_id, param.work_id, param.text_chunks, param.from_language, param.to_language, param.secure); } diff --git a/chrome/browser/renderer_host/translation_service.cc b/chrome/browser/renderer_host/translation_service.cc index af5e7a9..db4441e 100644 --- a/chrome/browser/renderer_host/translation_service.cc +++ b/chrome/browser/renderer_host/translation_service.cc @@ -4,26 +4,544 @@ #include "chrome/browser/renderer_host/translation_service.h" -#include "base/string_util.h" -#include "chrome/browser/renderer_host/resource_message_filter.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" -TranslationService::TranslationService(ResourceMessageFilter* filter) - : resource_message_filter_(filter) { +#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"; + +// Describes languages deemed equivalent from a translation point of view. +// This is used to detect unnecessary translations. +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" }, +}; + +// 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 + +// 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) { +} + +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 std::vector<string16>& text_chunks, - std::string from_language, - std::string to_language, + const TextChunks& text_chunks, + const std::string& source_lang, + const std::string& target_lang, bool secure) { - std::vector<string16> translated_text; - for (std::vector<string16>::const_iterator iter = text_chunks.begin(); - iter != text_chunks.end(); ++iter) { - translated_text.push_back(StringToUpperASCII(*iter)); - } - resource_message_filter_->Send( - new ViewMsg_TranslateTextReponse(routing_id, work_id, - 0, translated_text)); + 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() >= + 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 str; + if (data.size() > 1U && data[0] == '"') { + str.append("["); + str.append(data); + str.append("]"); + } + scoped_ptr<Value> value(base::JSONReader::Read(str.empty() ? data : str, + 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 str16; + if (!value->GetAsUTF16(&str16)) { + NOTREACHED(); + TranslationFailed(source); + return; + } + TextChunks text_chunks; + text_chunks.push_back(str16); + translated_chunks_list.push_back(text_chunks); + } else { + if (!value->IsType(Value::TYPE_LIST)) { + NOTREACHED() << "Translation server returned unexpected JSON response " + " (not a list)."; + TranslationFailed(source); + return; + } + ListValue* list = static_cast<ListValue*>(value.get()); + for (size_t i = 0; i < list->GetSize(); ++i) { + string16 translated_text; + if (!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 text_chunks; + TranslationService::SplitTextChunks(translated_text, &text_chunks); + translated_chunks_list.push_back(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::ShouldTranslatePage( + const std::string& page_language, const std::string& chrome_language) { + // Most locale names are the actual ISO 639 codes that the Google translate + // API uses, but for the ones longer than 2 chars. + // See l10n_util.cc for the list. + for (size_t i = 0; i < arraysize(kLocaleToCLDLanguages); ++i) { + if (chrome_language == kLocaleToCLDLanguages[i].locale_language && + page_language == kLocaleToCLDLanguages[i].cld_language) { + return false; + } + } + return true; +} + +// static +bool TranslationService::IsTranslationEnabled() { + return GURL(kServiceURL).host() != "disabled"; +} + +//////////////////////////////////////////////////////////////////////////////// +// 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()); +} + +// static +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(ASCIIToUTF16("<a _CR_TR_ id='")); + str.append(IntToString16(i)); + str.append(ASCIIToUTF16("'>")); + str.append(text_chunks[i]); + str.append(ASCIIToUTF16("</a>")); + } + return str; +} + +// static +void TranslationService::SplitTextChunks(const string16& translated_text, + TextChunks* text_chunks) { + const string16 kOpenTag = ASCIIToUTF16("<a _CR_TR_ "); + const string16 kCloseTag = ASCIIToUTF16("</a>"); + const size_t open_tag_len = kOpenTag.size(); + + size_t start_index = translated_text.find(kOpenTag); + if (start_index == std::string::npos) { + // 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 the open tag to the next open tag located after at least + // one close tag. + while (start_index != std::string::npos) { + size_t stop_index = + translated_text.find(kCloseTag, start_index + open_tag_len); + string16 chunk; + if (stop_index == std::string::npos) { + // No close tag. Just report as one chunk. + chunk = translated_text; + start_index = std::string::npos; // So we break on next iteration. + } else { + // Now find the next open tag after this close tag. + stop_index = translated_text.find(kOpenTag, stop_index); + if (stop_index != std::string::npos) { + chunk = translated_text.substr(start_index, stop_index - start_index); + start_index = stop_index; + } else { + chunk = translated_text.substr(start_index); + start_index = std::string::npos; // So we break on next iteration. + } + } + chunk = RemoveTag(chunk); + // The translation server leaves some ampersand character in the + // translation. + chunk = UnescapeForHTML(chunk); + text_chunks->push_back(RemoveTag(chunk)); + } +} + +// static +string16 TranslationService::RemoveTag(const string16& text) { + // Remove any anchor tags, knowing they could be extra/unbalanced tags. + const string16 kStartTag(ASCIIToUTF16("<a ")); + const string16 kEndTag(ASCIIToUTF16("</a>")); + const string16 kGreaterThan(ASCIIToUTF16(">")); + const string16 kLessThan(ASCIIToUTF16("<")); + + string16 result; + size_t start_index = text.find(kStartTag); + 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(kStartTag, 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, + ASCIIToUTF16("</a>"), ASCIIToUTF16("")); + 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"); + } + } + 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 index a8e8429..bcf6322 100644 --- a/chrome/browser/renderer_host/translation_service.h +++ b/chrome/browser/renderer_host/translation_service.h @@ -5,30 +5,154 @@ #ifndef CHROME_BROWSER_RENDERER_HOST_TRANSLATION_SERVICE_H_ #define CHROME_BROWSER_RENDERER_HOST_TRANSLATION_SERVICE_H_ +#include <map> #include <string> #include <vector> +#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 ResourceMessageFilter; +class DictionaryValue; +class TranslationServiceTest; +class TranslateURLFetcherDelegate; +class URLFetcher; // The TranslationService class is used to translate text. -// This temporary implementation only upcases the text sent to it. -class TranslationService { +// There is one TranslationService is 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(ResourceMessageFilter* resource_msg_filter); + explicit TranslationService(IPC::Message::Sender* message_sender); + virtual ~TranslationService(); - // Translates the passed text chunks and sends a + // 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, - std::string from_language, - std::string to_language, + 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 a page in the language |page_language| (as reported by the + // CLD) should be translated when Chrome is using |chrome_language|. Note that + // this returns false for similar languages, for example it returns false when + // given the values 'en' and 'en-US'. + static bool ShouldTranslatePage(const std::string& page_language, + const std::string& chrome_language); + + // Returns true if the TranslationService is enabled. + static bool IsTranslationEnabled(); + + 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: - ResourceMessageFilter* resource_message_filter_; + friend class TranslationServiceTest; + friend class TranslateURLFetcherDelegate; + FRIEND_TEST(TranslationServiceTest, MergeTestChunks); + FRIEND_TEST(TranslationServiceTest, SplitTestChunks); + 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. + static 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. + static void SplitTextChunks(const string16& translated_text, + TextChunks* text_chunks); + + // Removes the HTML anchor tag surrounding |text| and returns the resulting + // string. + static string16 RemoveTag(const string16& text); + + // 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_; DISALLOW_COPY_AND_ASSIGN(TranslationService); }; diff --git a/chrome/browser/renderer_host/translation_service_unittest.cc b/chrome/browser/renderer_host/translation_service_unittest.cc new file mode 100644 index 0000000..70a438f --- /dev/null +++ b/chrome/browser/renderer_host/translation_service_unittest.cc @@ -0,0 +1,458 @@ +// 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) { + std::vector<string16> input; + input.push_back(ASCIIToUTF16("Hello")); + string16 result = TranslationService::MergeTextChunks(input); + EXPECT_EQ(ASCIIToUTF16("Hello"), result); + input.push_back(ASCIIToUTF16(" my name")); + input.push_back(ASCIIToUTF16(" is")); + input.push_back(ASCIIToUTF16(" Jay.")); + result = TranslationService::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" + }; + + 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 = TranslationService::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, SplitTestChunks) { + // Simple case. + std::vector<string16> text_chunks; + TranslationService::SplitTextChunks(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. + TranslationService::SplitTextChunks( + 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> + TranslationService::SplitTextChunks( + 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 One, "), text_chunks[1]); + EXPECT_EQ(ASCIIToUTF16("the new Android Phone"), text_chunks[2]); + text_chunks.clear(); +} + +// 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, than 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/tab_contents/tab_contents.cc b/chrome/browser/tab_contents/tab_contents.cc index 2f94369..310f2e9 100644 --- a/chrome/browser/tab_contents/tab_contents.cc +++ b/chrome/browser/tab_contents/tab_contents.cc @@ -45,6 +45,7 @@ #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/sessions/session_types.h" @@ -1807,6 +1808,19 @@ void TabContents::OnPageContents(const GURL& url, entry->set_language(language); } + if (CommandLine::ForCurrentProcess()->HasSwitch( + switches::kAutoPageTranslate) && + TranslationService::IsTranslationEnabled()) { + std::string locale = g_browser_process->GetApplicationLocale(); + if (!locale.empty() && locale != language) { + // Don't translate the NTP, download page, history... + if (entry && !entry->url().SchemeIs("chrome") && + TranslationService::ShouldTranslatePage(language, locale)) { + render_view_host()->TranslatePage(entry->page_id(), language, locale); + } + } + } + std::string lang = language; NotificationService::current()->Notify( NotificationType::TAB_LANGUAGE_DETERMINED, |