summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorjcampan@chromium.org <jcampan@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-01-29 01:44:42 +0000
committerjcampan@chromium.org <jcampan@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-01-29 01:44:42 +0000
commitd4a00a73c0349128e4ad9d5f30704ba2ec1d79d8 (patch)
tree800d83b966fbafbc8f18f442ced1fdd31e6b3536
parentaef2da3e36d775461f71c104c28f3ca4ff8f7b9a (diff)
downloadchromium_src-d4a00a73c0349128e4ad9d5f30704ba2ec1d79d8.zip
chromium_src-d4a00a73c0349128e4ad9d5f30704ba2ec1d79d8.tar.gz
chromium_src-d4a00a73c0349128e4ad9d5f30704ba2ec1d79d8.tar.bz2
This CL makes the TranslationService class send the text to be translated to the translation
server. It groups requests as to limit the number of requests sent to the server. Also this CL adds a flag to automatically turn on translation on pages that are not in the language Chrome is configured in. BUG=None TEST=Run the unit-tests. Add the --auto-translate flag then navigate to pages in a language which is not the language Chrome is configured. They should get translated. Review URL: http://codereview.chromium.org/552216 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@37479 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/browser/net/test_url_fetcher_factory.h4
-rw-r--r--chrome/browser/net/url_fetcher.cc4
-rw-r--r--chrome/browser/net/url_fetcher.h5
-rw-r--r--chrome/browser/renderer_host/render_process_host.h2
-rw-r--r--chrome/browser/renderer_host/render_view_host.cc7
-rw-r--r--chrome/browser/renderer_host/render_view_host.h6
-rw-r--r--chrome/browser/renderer_host/resource_message_filter.cc2
-rw-r--r--chrome/browser/renderer_host/translation_service.cc548
-rw-r--r--chrome/browser/renderer_host/translation_service.h140
-rw-r--r--chrome/browser/renderer_host/translation_service_unittest.cc458
-rw-r--r--chrome/browser/tab_contents/tab_contents.cc14
-rwxr-xr-xchrome/chrome_tests.gypi1
-rw-r--r--chrome/common/chrome_switches.cc4
-rw-r--r--chrome/common/chrome_switches.h1
-rw-r--r--chrome/common/render_messages.h7
-rw-r--r--chrome/common/render_messages_internal.h9
-rw-r--r--chrome/renderer/render_view.cc19
-rw-r--r--chrome/renderer/render_view.h15
-rw-r--r--chrome/renderer/translate/page_translator.cc6
-rw-r--r--chrome/renderer/translate/text_translator_impl.cc1
20 files changed, 1219 insertions, 34 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,
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi
index d5439fe..74274ac 100755
--- a/chrome/chrome_tests.gypi
+++ b/chrome/chrome_tests.gypi
@@ -764,6 +764,7 @@
'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',
diff --git a/chrome/common/chrome_switches.cc b/chrome/common/chrome_switches.cc
index fdd84c4..7b99cdf 100644
--- a/chrome/common/chrome_switches.cc
+++ b/chrome/common/chrome_switches.cc
@@ -31,6 +31,10 @@ const char kApp[] = "app";
// automation-related messages on IPC channel with the given ID.
const char kAutomationClientChannelID[] = "automation-channel";
+// Makes Chrome translate any page loaded which is not in the locale Chrome
+// is running in.
+const char kAutoPageTranslate[] = "auto-translate";
+
// Enables the bookmark menu.
const char kBookmarkMenu[] = "bookmark-menu";
diff --git a/chrome/common/chrome_switches.h b/chrome/common/chrome_switches.h
index b814aa0..e29b7f8 100644
--- a/chrome/common/chrome_switches.h
+++ b/chrome/common/chrome_switches.h
@@ -24,6 +24,7 @@ extern const char kAllowSandboxDebugging[];
extern const char kAlwaysEnableDevTools[];
extern const char kApp[];
extern const char kAutomationClientChannelID[];
+extern const char kAutoPageTranslate[];
extern const char kBookmarkMenu[];
extern const char kBrowserAssertTest[];
extern const char kBrowserCrashTest[];
diff --git a/chrome/common/render_messages.h b/chrome/common/render_messages.h
index ebc7af4..b50b0d1 100644
--- a/chrome/common/render_messages.h
+++ b/chrome/common/render_messages.h
@@ -581,6 +581,9 @@ struct ViewHostMsg_TranslateTextParam {
// 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;
@@ -2457,6 +2460,7 @@ struct ParamTraits<ViewHostMsg_TranslateTextParam> {
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);
@@ -2467,6 +2471,7 @@ struct ParamTraits<ViewHostMsg_TranslateTextParam> {
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) &&
@@ -2478,6 +2483,8 @@ struct ParamTraits<ViewHostMsg_TranslateTextParam> {
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);
diff --git a/chrome/common/render_messages_internal.h b/chrome/common/render_messages_internal.h
index 696e5e9..e51f867 100644
--- a/chrome/common/render_messages_internal.h
+++ b/chrome/common/render_messages_internal.h
@@ -843,6 +843,15 @@ IPC_BEGIN_MESSAGES(View)
IPC_MESSAGE_ROUTED1(ViewMsg_CustomContextMenuAction,
unsigned /* action */)
+ // Tells the renderer to translate the page contents from one language to
+ // another.
+ IPC_MESSAGE_ROUTED3(ViewMsg_TranslatePage,
+ int /* page id */,
+ 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,
diff --git a/chrome/renderer/render_view.cc b/chrome/renderer/render_view.cc
index ce5520b..a27792f4 100644
--- a/chrome/renderer/render_view.cc
+++ b/chrome/renderer/render_view.cc
@@ -568,6 +568,7 @@ void RenderView::OnMessageReceived(const IPC::Message& message) {
OnExecuteCode)
IPC_MESSAGE_HANDLER(ViewMsg_CustomContextMenuAction,
OnCustomContextMenuAction)
+ IPC_MESSAGE_HANDLER(ViewMsg_TranslatePage, OnTranslatePage)
IPC_MESSAGE_HANDLER(ViewMsg_TranslateTextReponse, OnTranslateTextResponse)
// Have the super handle all other messages.
@@ -3315,13 +3316,21 @@ void RenderView::OnCustomContextMenuAction(unsigned action) {
webview()->performCustomContextMenuAction(action);
}
+void RenderView::OnTranslatePage(int page_id,
+ 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_->Translate(main_frame, source_lang, target_lang);
+}
+
void RenderView::OnTranslateTextResponse(
int work_id, int error_id, const std::vector<string16>& text_chunks) {
- if (error_id) {
- page_translator_->TranslationError(work_id, error_id);
- return;
- }
- page_translator_->TextTranslated(work_id, text_chunks);
+ text_translator_.OnTranslationResponse(work_id, error_id, text_chunks);
}
void RenderView::OnInstallMissingPlugin() {
diff --git a/chrome/renderer/render_view.h b/chrome/renderer/render_view.h
index 2845601..dfc4cdb 100644
--- a/chrome/renderer/render_view.h
+++ b/chrome/renderer/render_view.h
@@ -159,18 +159,22 @@ class RenderView : public RenderWidget,
return host_window_;
}
- int browser_window_id() {
+ int browser_window_id() const {
return browser_window_id_;
}
- ViewType::Type view_type() {
+ ViewType::Type view_type() const {
return view_type_;
}
- PrintWebViewHelper* print_helper() {
+ PrintWebViewHelper* print_helper() const {
return print_helper_.get();
}
+ int page_id() const {
+ return page_id_;
+ }
+
// IPC::Channel::Listener
virtual void OnMessageReceived(const IPC::Message& msg);
@@ -690,6 +694,11 @@ class RenderView : public RenderWidget,
// Execute custom context menu action.
void OnCustomContextMenuAction(unsigned action);
+ // Tells the renderer to translate the page contents.
+ void OnTranslatePage(int page_id,
+ 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,
diff --git a/chrome/renderer/translate/page_translator.cc b/chrome/renderer/translate/page_translator.cc
index 1bb245a..8119a8d 100644
--- a/chrome/renderer/translate/page_translator.cc
+++ b/chrome/renderer/translate/page_translator.cc
@@ -16,6 +16,8 @@
#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",
@@ -25,6 +27,7 @@ const char* const kSkippedTags[] = { "APPLET", "AREA", "BASE", "FRAME",
// 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" };
+}
// Returns true when s1 < s2.
bool PageTranslator::WebStringCompare::operator()(
@@ -115,7 +118,8 @@ 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()) {
- NOTREACHED() << "Translation results received for unknown node zone";
+ // 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;
}
diff --git a/chrome/renderer/translate/text_translator_impl.cc b/chrome/renderer/translate/text_translator_impl.cc
index 40e2c16..d2b668c 100644
--- a/chrome/renderer/translate/text_translator_impl.cc
+++ b/chrome/renderer/translate/text_translator_impl.cc
@@ -28,6 +28,7 @@ int TextTranslatorImpl::Translate(const std::vector<string16>& text,
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;