summaryrefslogtreecommitdiffstats
path: root/chrome/browser
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/browser')
-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
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,