summaryrefslogtreecommitdiffstats
path: root/components/translate
diff options
context:
space:
mode:
authordroger <droger@chromium.org>2014-09-17 07:54:05 -0700
committerCommit bot <commit-bot@chromium.org>2014-09-17 14:54:15 +0000
commitf1da1804fb44d2c36fe10d11808c1cfc0c369fcf (patch)
treeaa8331e375be892fc299e3ce8f647a0ba7a60c74 /components/translate
parent2a3abf4b24bce7692cf41f0092934b6e48a19dee (diff)
downloadchromium_src-f1da1804fb44d2c36fe10d11808c1cfc0c369fcf.zip
chromium_src-f1da1804fb44d2c36fe10d11808c1cfc0c369fcf.tar.gz
chromium_src-f1da1804fb44d2c36fe10d11808c1cfc0c369fcf.tar.bz2
Move TranslateHelper to the translate component.
This CL moves TranslateHelper to the translate component. A couple dependencies had to be injected, through the constructor: - extensions (group and scheme) - isolated world id BUG=335087 TBR=jochen Review URL: https://codereview.chromium.org/564793003 Cr-Commit-Position: refs/heads/master@{#295276}
Diffstat (limited to 'components/translate')
-rw-r--r--components/translate/content/renderer/BUILD.gn9
-rw-r--r--components/translate/content/renderer/DEPS2
-rw-r--r--components/translate/content/renderer/translate_helper.cc638
-rw-r--r--components/translate/content/renderer/translate_helper.h284
4 files changed, 933 insertions, 0 deletions
diff --git a/components/translate/content/renderer/BUILD.gn b/components/translate/content/renderer/BUILD.gn
index 5651dbd..db461be 100644
--- a/components/translate/content/renderer/BUILD.gn
+++ b/components/translate/content/renderer/BUILD.gn
@@ -7,12 +7,21 @@ import("//build/config/features.gni")
static_library("renderer") {
sources = [
"renderer_cld_data_provider.h",
+ "translate_helper.cc",
+ "translate_helper.h",
]
deps = [
"//base",
+ "//components/translate/content/common",
+ "//components/translate/core/common",
+ "//components/translate/core/language_detection",
"//content/public/common",
+ "//content/public/renderer",
"//ipc",
+ "//third_party/WebKit/public:blink",
+ "//url",
+ "//v8",
]
if (cld_version == 0 || cld_version == 2) {
diff --git a/components/translate/content/renderer/DEPS b/components/translate/content/renderer/DEPS
index 826b97f..a0023b4 100644
--- a/components/translate/content/renderer/DEPS
+++ b/components/translate/content/renderer/DEPS
@@ -1,4 +1,6 @@
include_rules = [
"+content/public/renderer",
"+third_party/cld_2",
+ "+third_party/WebKit/public/web",
+ "+v8",
]
diff --git a/components/translate/content/renderer/translate_helper.cc b/components/translate/content/renderer/translate_helper.cc
new file mode 100644
index 0000000..5aff9c8
--- /dev/null
+++ b/components/translate/content/renderer/translate_helper.cc
@@ -0,0 +1,638 @@
+// Copyright (c) 2012 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 "components/translate/content/renderer/translate_helper.h"
+
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "base/metrics/histogram.h"
+#include "base/strings/string16.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "components/translate/content/common/translate_messages.h"
+#include "components/translate/core/common/translate_constants.h"
+#include "components/translate/core/common/translate_metrics.h"
+#include "components/translate/core/common/translate_util.h"
+#include "components/translate/core/language_detection/language_detection_util.h"
+#include "content/public/common/content_constants.h"
+#include "content/public/common/url_constants.h"
+#include "content/public/renderer/render_thread.h"
+#include "content/public/renderer/render_view.h"
+#include "ipc/ipc_platform_file.h"
+#include "third_party/WebKit/public/web/WebDocument.h"
+#include "third_party/WebKit/public/web/WebElement.h"
+#include "third_party/WebKit/public/web/WebFrame.h"
+#include "third_party/WebKit/public/web/WebNode.h"
+#include "third_party/WebKit/public/web/WebNodeList.h"
+#include "third_party/WebKit/public/web/WebScriptSource.h"
+#include "third_party/WebKit/public/web/WebView.h"
+#include "third_party/WebKit/public/web/WebWidget.h"
+#include "url/gurl.h"
+#include "v8/include/v8.h"
+
+using base::ASCIIToUTF16;
+using blink::WebDocument;
+using blink::WebElement;
+using blink::WebFrame;
+using blink::WebNode;
+using blink::WebNodeList;
+using blink::WebScriptSource;
+using blink::WebSecurityOrigin;
+using blink::WebString;
+using blink::WebVector;
+using blink::WebView;
+
+namespace {
+
+// The delay in milliseconds that we'll wait before checking to see if the
+// translate library injected in the page is ready.
+const int kTranslateInitCheckDelayMs = 150;
+
+// The maximum number of times we'll check to see if the translate library
+// injected in the page is ready.
+const int kMaxTranslateInitCheckAttempts = 5;
+
+// The delay we wait in milliseconds before checking whether the translation has
+// finished.
+const int kTranslateStatusCheckDelayMs = 400;
+
+// Language name passed to the Translate element for it to detect the language.
+const char kAutoDetectionLanguage[] = "auto";
+
+// Isolated world sets following content-security-policy.
+const char kContentSecurityPolicy[] = "script-src 'self' 'unsafe-eval'";
+
+// Whether or not we have set the CLD callback yet.
+bool g_cld_callback_set = false;
+
+} // namespace
+
+namespace translate {
+
+////////////////////////////////////////////////////////////////////////////////
+// TranslateHelper, public:
+//
+TranslateHelper::TranslateHelper(content::RenderView* render_view,
+ int world_id,
+ int extension_group,
+ const std::string& extension_scheme)
+ : content::RenderViewObserver(render_view),
+ page_seq_no_(0),
+ translation_pending_(false),
+ cld_data_provider_(translate::CreateRendererCldDataProviderFor(this)),
+ cld_data_polling_started_(false),
+ cld_data_polling_canceled_(false),
+ deferred_page_capture_(false),
+ deferred_page_seq_no_(-1),
+ world_id_(world_id),
+ extension_group_(extension_group),
+ extension_scheme_(extension_scheme),
+ weak_method_factory_(this) {
+}
+
+TranslateHelper::~TranslateHelper() {
+ CancelPendingTranslation();
+ CancelCldDataPolling();
+}
+
+void TranslateHelper::PrepareForUrl(const GURL& url) {
+ ++page_seq_no_;
+ Send(new ChromeViewHostMsg_TranslateAssignedSequenceNumber(
+ routing_id(), page_seq_no_));
+ deferred_page_capture_ = false;
+ deferred_page_seq_no_ = -1;
+ deferred_contents_.clear();
+ if (cld_data_polling_started_)
+ return;
+
+ // TODO(andrewhayden): Refactor translate_manager.cc's IsTranslatableURL to
+ // components/translate/core/common/translate_util.cc, and ignore any URL
+ // that fails that check. This will require moving unit tests and rewiring
+ // other function calls as well, so for now replicate the logic here.
+ if (url.is_empty())
+ return;
+ if (url.SchemeIs(content::kChromeUIScheme))
+ return;
+ if (url.SchemeIs(content::kChromeDevToolsScheme))
+ return;
+ if (url.SchemeIs(url::kFtpScheme))
+ return;
+ if (url.SchemeIs(extension_scheme_.c_str()))
+ return;
+
+ // Start polling for CLD data.
+ cld_data_polling_started_ = true;
+ TranslateHelper::SendCldDataRequest(0, 1000);
+}
+
+void TranslateHelper::PageCaptured(const base::string16& contents) {
+ PageCapturedImpl(page_seq_no_, contents);
+}
+
+void TranslateHelper::PageCapturedImpl(int page_seq_no,
+ const base::string16& contents) {
+ // Get the document language as set by WebKit from the http-equiv
+ // meta tag for "content-language". This may or may not also
+ // have a value derived from the actual Content-Language HTTP
+ // header. The two actually have different meanings (despite the
+ // original intent of http-equiv to be an equivalent) with the former
+ // being the language of the document and the latter being the
+ // language of the intended audience (a distinction really only
+ // relevant for things like langauge textbooks). This distinction
+ // shouldn't affect translation.
+ WebFrame* main_frame = GetMainFrame();
+ if (!main_frame || page_seq_no_ != page_seq_no)
+ return;
+
+ if (!cld_data_provider_->IsCldDataAvailable()) {
+ // We're in dynamic mode and CLD data isn't loaded. Retry when CLD data
+ // is loaded, if ever.
+ deferred_page_capture_ = true;
+ deferred_page_seq_no_ = page_seq_no;
+ deferred_contents_ = contents;
+ RecordLanguageDetectionTiming(DEFERRED);
+ return;
+ }
+
+ if (deferred_page_seq_no_ == -1) {
+ // CLD data was available before language detection was requested.
+ RecordLanguageDetectionTiming(ON_TIME);
+ } else {
+ // This is a request that was triggered because CLD data is now available
+ // and was previously deferred.
+ RecordLanguageDetectionTiming(RESUMED);
+ }
+
+ WebDocument document = main_frame->document();
+ std::string content_language = document.contentLanguage().utf8();
+ WebElement html_element = document.documentElement();
+ std::string html_lang;
+ // |html_element| can be null element, e.g. in
+ // BrowserTest.WindowOpenClose.
+ if (!html_element.isNull())
+ html_lang = html_element.getAttribute("lang").utf8();
+ std::string cld_language;
+ bool is_cld_reliable;
+ std::string language = DeterminePageLanguage(
+ content_language, html_lang, contents, &cld_language, &is_cld_reliable);
+
+ if (language.empty())
+ return;
+
+ language_determined_time_ = base::TimeTicks::Now();
+
+ GURL url(document.url());
+ LanguageDetectionDetails details;
+ details.time = base::Time::Now();
+ details.url = url;
+ details.content_language = content_language;
+ details.cld_language = cld_language;
+ details.is_cld_reliable = is_cld_reliable;
+ details.html_root_language = html_lang;
+ details.adopted_language = language;
+
+ // TODO(hajimehoshi): If this affects performance, it should be set only if
+ // translate-internals tab exists.
+ details.contents = contents;
+
+ Send(new ChromeViewHostMsg_TranslateLanguageDetermined(
+ routing_id(),
+ details,
+ IsTranslationAllowed(&document) && !language.empty()));
+}
+
+void TranslateHelper::CancelPendingTranslation() {
+ weak_method_factory_.InvalidateWeakPtrs();
+ translation_pending_ = false;
+ source_lang_.clear();
+ target_lang_.clear();
+ CancelCldDataPolling();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// TranslateHelper, protected:
+//
+bool TranslateHelper::IsTranslateLibAvailable() {
+ return ExecuteScriptAndGetBoolResult(
+ "typeof cr != 'undefined' && typeof cr.googleTranslate != 'undefined' && "
+ "typeof cr.googleTranslate.translate == 'function'", false);
+}
+
+bool TranslateHelper::IsTranslateLibReady() {
+ return ExecuteScriptAndGetBoolResult("cr.googleTranslate.libReady", false);
+}
+
+bool TranslateHelper::HasTranslationFinished() {
+ return ExecuteScriptAndGetBoolResult("cr.googleTranslate.finished", true);
+}
+
+bool TranslateHelper::HasTranslationFailed() {
+ return ExecuteScriptAndGetBoolResult("cr.googleTranslate.error", true);
+}
+
+bool TranslateHelper::StartTranslation() {
+ std::string script = "cr.googleTranslate.translate('" +
+ source_lang_ +
+ "','" +
+ target_lang_ +
+ "')";
+ return ExecuteScriptAndGetBoolResult(script, false);
+}
+
+std::string TranslateHelper::GetOriginalPageLanguage() {
+ return ExecuteScriptAndGetStringResult("cr.googleTranslate.sourceLang");
+}
+
+base::TimeDelta TranslateHelper::AdjustDelay(int delayInMs) {
+ // Just converts |delayInMs| without any modification in practical cases.
+ // Tests will override this function to return modified value.
+ return base::TimeDelta::FromMilliseconds(delayInMs);
+}
+
+void TranslateHelper::ExecuteScript(const std::string& script) {
+ WebFrame* main_frame = GetMainFrame();
+ if (!main_frame)
+ return;
+
+ WebScriptSource source = WebScriptSource(ASCIIToUTF16(script));
+ main_frame->executeScriptInIsolatedWorld(
+ world_id_, &source, 1, extension_group_);
+}
+
+bool TranslateHelper::ExecuteScriptAndGetBoolResult(const std::string& script,
+ bool fallback) {
+ WebFrame* main_frame = GetMainFrame();
+ if (!main_frame)
+ return fallback;
+
+ v8::HandleScope handle_scope(v8::Isolate::GetCurrent());
+ WebVector<v8::Local<v8::Value> > results;
+ WebScriptSource source = WebScriptSource(ASCIIToUTF16(script));
+ main_frame->executeScriptInIsolatedWorld(
+ world_id_, &source, 1, extension_group_, &results);
+ if (results.size() != 1 || results[0].IsEmpty() || !results[0]->IsBoolean()) {
+ NOTREACHED();
+ return fallback;
+ }
+
+ return results[0]->BooleanValue();
+}
+
+std::string TranslateHelper::ExecuteScriptAndGetStringResult(
+ const std::string& script) {
+ WebFrame* main_frame = GetMainFrame();
+ if (!main_frame)
+ return std::string();
+
+ v8::HandleScope handle_scope(v8::Isolate::GetCurrent());
+ WebVector<v8::Local<v8::Value> > results;
+ WebScriptSource source = WebScriptSource(ASCIIToUTF16(script));
+ main_frame->executeScriptInIsolatedWorld(
+ world_id_, &source, 1, extension_group_, &results);
+ if (results.size() != 1 || results[0].IsEmpty() || !results[0]->IsString()) {
+ NOTREACHED();
+ return std::string();
+ }
+
+ v8::Local<v8::String> v8_str = results[0]->ToString();
+ int length = v8_str->Utf8Length() + 1;
+ scoped_ptr<char[]> str(new char[length]);
+ v8_str->WriteUtf8(str.get(), length);
+ return std::string(str.get());
+}
+
+double TranslateHelper::ExecuteScriptAndGetDoubleResult(
+ const std::string& script) {
+ WebFrame* main_frame = GetMainFrame();
+ if (!main_frame)
+ return 0.0;
+
+ v8::HandleScope handle_scope(v8::Isolate::GetCurrent());
+ WebVector<v8::Local<v8::Value> > results;
+ WebScriptSource source = WebScriptSource(ASCIIToUTF16(script));
+ main_frame->executeScriptInIsolatedWorld(
+ world_id_, &source, 1, extension_group_, &results);
+ if (results.size() != 1 || results[0].IsEmpty() || !results[0]->IsNumber()) {
+ NOTREACHED();
+ return 0.0;
+ }
+
+ return results[0]->NumberValue();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// TranslateHelper, private:
+//
+
+// static
+bool TranslateHelper::IsTranslationAllowed(WebDocument* document) {
+ WebElement head = document->head();
+ if (head.isNull() || !head.hasChildNodes())
+ return true;
+
+ const WebString meta(ASCIIToUTF16("meta"));
+ const WebString name(ASCIIToUTF16("name"));
+ const WebString google(ASCIIToUTF16("google"));
+ const WebString value(ASCIIToUTF16("value"));
+ const WebString content(ASCIIToUTF16("content"));
+
+ WebNodeList children = head.childNodes();
+ for (size_t i = 0; i < children.length(); ++i) {
+ WebNode node = children.item(i);
+ if (!node.isElementNode())
+ continue;
+ WebElement element = node.to<WebElement>();
+ // Check if a tag is <meta>.
+ if (!element.hasHTMLTagName(meta))
+ continue;
+ // Check if the tag contains name="google".
+ WebString attribute = element.getAttribute(name);
+ if (attribute.isNull() || attribute != google)
+ continue;
+ // Check if the tag contains value="notranslate", or content="notranslate".
+ attribute = element.getAttribute(value);
+ if (attribute.isNull())
+ attribute = element.getAttribute(content);
+ if (attribute.isNull())
+ continue;
+ if (LowerCaseEqualsASCII(attribute, "notranslate"))
+ return false;
+ }
+ return true;
+}
+
+bool TranslateHelper::OnMessageReceived(const IPC::Message& message) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP(TranslateHelper, message)
+ IPC_MESSAGE_HANDLER(ChromeViewMsg_TranslatePage, OnTranslatePage)
+ IPC_MESSAGE_HANDLER(ChromeViewMsg_RevertTranslation, OnRevertTranslation)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+ if (!handled) {
+ handled = cld_data_provider_->OnMessageReceived(message);
+ }
+ return handled;
+}
+
+void TranslateHelper::OnTranslatePage(int page_seq_no,
+ const std::string& translate_script,
+ const std::string& source_lang,
+ const std::string& target_lang) {
+ WebFrame* main_frame = GetMainFrame();
+ if (!main_frame || page_seq_no_ != page_seq_no)
+ return; // We navigated away, nothing to do.
+
+ // A similar translation is already under way, nothing to do.
+ if (translation_pending_ && target_lang_ == target_lang)
+ return;
+
+ // Any pending translation is now irrelevant.
+ CancelPendingTranslation();
+
+ // Set our states.
+ translation_pending_ = true;
+
+ // If the source language is undetermined, we'll let the translate element
+ // detect it.
+ source_lang_ = (source_lang != kUnknownLanguageCode) ? source_lang
+ : kAutoDetectionLanguage;
+ target_lang_ = target_lang;
+
+ ReportUserActionDuration(language_determined_time_, base::TimeTicks::Now());
+
+ GURL url(main_frame->document().url());
+ ReportPageScheme(url.scheme());
+
+ // Set up v8 isolated world with proper content-security-policy and
+ // security-origin.
+ WebFrame* frame = GetMainFrame();
+ if (frame) {
+ frame->setIsolatedWorldContentSecurityPolicy(
+ world_id_, WebString::fromUTF8(kContentSecurityPolicy));
+
+ GURL security_origin = GetTranslateSecurityOrigin();
+ frame->setIsolatedWorldSecurityOrigin(
+ world_id_, WebSecurityOrigin::create(security_origin));
+ }
+
+ if (!IsTranslateLibAvailable()) {
+ // Evaluate the script to add the translation related method to the global
+ // context of the page.
+ ExecuteScript(translate_script);
+ DCHECK(IsTranslateLibAvailable());
+ }
+
+ TranslatePageImpl(page_seq_no, 0);
+}
+
+void TranslateHelper::OnRevertTranslation(int page_seq_no) {
+ if (page_seq_no_ != page_seq_no)
+ return; // We navigated away, nothing to do.
+
+ if (!IsTranslateLibAvailable()) {
+ NOTREACHED();
+ return;
+ }
+
+ CancelPendingTranslation();
+
+ ExecuteScript("cr.googleTranslate.revert()");
+}
+
+void TranslateHelper::CheckTranslateStatus(int page_seq_no) {
+ // If this is not the same page, the translation has been canceled. If the
+ // view is gone, the page is closing.
+ if (page_seq_no_ != page_seq_no || !render_view()->GetWebView())
+ return;
+
+ // First check if there was an error.
+ if (HasTranslationFailed()) {
+ // TODO(toyoshim): Check |errorCode| of translate.js and notify it here.
+ NotifyBrowserTranslationFailed(TranslateErrors::TRANSLATION_ERROR);
+ return; // There was an error.
+ }
+
+ if (HasTranslationFinished()) {
+ std::string actual_source_lang;
+ // Translation was successfull, if it was auto, retrieve the source
+ // language the Translate Element detected.
+ if (source_lang_ == kAutoDetectionLanguage) {
+ actual_source_lang = GetOriginalPageLanguage();
+ if (actual_source_lang.empty()) {
+ NotifyBrowserTranslationFailed(TranslateErrors::UNKNOWN_LANGUAGE);
+ return;
+ } else if (actual_source_lang == target_lang_) {
+ NotifyBrowserTranslationFailed(TranslateErrors::IDENTICAL_LANGUAGES);
+ return;
+ }
+ } else {
+ actual_source_lang = source_lang_;
+ }
+
+ if (!translation_pending_) {
+ NOTREACHED();
+ return;
+ }
+
+ translation_pending_ = false;
+
+ // Check JavaScript performance counters for UMA reports.
+ ReportTimeToTranslate(
+ ExecuteScriptAndGetDoubleResult("cr.googleTranslate.translationTime"));
+
+ // Notify the browser we are done.
+ render_view()->Send(
+ new ChromeViewHostMsg_PageTranslated(render_view()->GetRoutingID(),
+ actual_source_lang,
+ target_lang_,
+ TranslateErrors::NONE));
+ return;
+ }
+
+ // The translation is still pending, check again later.
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&TranslateHelper::CheckTranslateStatus,
+ weak_method_factory_.GetWeakPtr(), page_seq_no),
+ AdjustDelay(kTranslateStatusCheckDelayMs));
+}
+
+void TranslateHelper::TranslatePageImpl(int page_seq_no, int count) {
+ DCHECK_LT(count, kMaxTranslateInitCheckAttempts);
+ if (page_seq_no_ != page_seq_no || !render_view()->GetWebView())
+ return;
+
+ if (!IsTranslateLibReady()) {
+ // The library is not ready, try again later, unless we have tried several
+ // times unsucessfully already.
+ if (++count >= kMaxTranslateInitCheckAttempts) {
+ NotifyBrowserTranslationFailed(TranslateErrors::INITIALIZATION_ERROR);
+ return;
+ }
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&TranslateHelper::TranslatePageImpl,
+ weak_method_factory_.GetWeakPtr(),
+ page_seq_no, count),
+ AdjustDelay(count * kTranslateInitCheckDelayMs));
+ return;
+ }
+
+ // The library is loaded, and ready for translation now.
+ // Check JavaScript performance counters for UMA reports.
+ ReportTimeToBeReady(
+ ExecuteScriptAndGetDoubleResult("cr.googleTranslate.readyTime"));
+ ReportTimeToLoad(
+ ExecuteScriptAndGetDoubleResult("cr.googleTranslate.loadTime"));
+
+ if (!StartTranslation()) {
+ NotifyBrowserTranslationFailed(TranslateErrors::TRANSLATION_ERROR);
+ return;
+ }
+ // Check the status of the translation.
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&TranslateHelper::CheckTranslateStatus,
+ weak_method_factory_.GetWeakPtr(), page_seq_no),
+ AdjustDelay(kTranslateStatusCheckDelayMs));
+}
+
+void TranslateHelper::NotifyBrowserTranslationFailed(
+ TranslateErrors::Type error) {
+ translation_pending_ = false;
+ // Notify the browser there was an error.
+ render_view()->Send(new ChromeViewHostMsg_PageTranslated(
+ render_view()->GetRoutingID(), source_lang_, target_lang_, error));
+}
+
+WebFrame* TranslateHelper::GetMainFrame() {
+ WebView* web_view = render_view()->GetWebView();
+
+ // When the tab is going to be closed, the web_view can be NULL.
+ if (!web_view)
+ return NULL;
+
+ return web_view->mainFrame();
+}
+
+void TranslateHelper::CancelCldDataPolling() {
+ cld_data_polling_canceled_ = true;
+}
+
+void TranslateHelper::SendCldDataRequest(const int delay_millis,
+ const int next_delay_millis) {
+ // Terminate immediately if told to stop polling.
+ if (cld_data_polling_canceled_)
+ return;
+
+ // Terminate immediately if data is already loaded.
+ if (cld_data_provider_->IsCldDataAvailable())
+ return;
+
+ if (!g_cld_callback_set) {
+ g_cld_callback_set = true;
+ cld_data_provider_->SetCldAvailableCallback(
+ base::Bind(&TranslateHelper::OnCldDataAvailable,
+ weak_method_factory_.GetWeakPtr()));
+ }
+
+ // Else, make an asynchronous request to get the data we need.
+ cld_data_provider_->SendCldDataRequest();
+
+ // ... and enqueue another delayed task to call again. This will start a
+ // chain of polling that will last until the pointer stops being NULL,
+ // which is the right thing to do.
+ // NB: In the great majority of cases, the data file will be available and
+ // the very first delayed task will be a no-op that terminates the chain.
+ // It's only while downloading the file that this will chain for a
+ // nontrivial amount of time.
+ // Use a weak pointer to avoid keeping this helper object around forever.
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&TranslateHelper::SendCldDataRequest,
+ weak_method_factory_.GetWeakPtr(),
+ next_delay_millis,
+ next_delay_millis),
+ base::TimeDelta::FromMilliseconds(delay_millis));
+}
+
+void TranslateHelper::OnCldDataAvailable() {
+ if (deferred_page_capture_) {
+ deferred_page_capture_ = false; // Don't do this a second time.
+ PageCapturedImpl(deferred_page_seq_no_, deferred_contents_);
+ deferred_page_seq_no_ = -1; // Clean up for sanity
+ deferred_contents_.clear(); // Clean up for sanity
+ }
+}
+
+void TranslateHelper::RecordLanguageDetectionTiming(
+ LanguageDetectionTiming timing) {
+ // The following comment is copied from page_load_histograms.cc, and applies
+ // just as equally here:
+ //
+ // Since there are currently no guarantees that renderer histograms will be
+ // sent to the browser, we initiate a PostTask here to be sure that we send
+ // the histograms we generated. Without this call, pages that don't have an
+ // on-close-handler might generate data that is lost when the renderer is
+ // shutdown abruptly (perchance because the user closed the tab).
+ DVLOG(1) << "Language detection timing: " << timing;
+ UMA_HISTOGRAM_ENUMERATION("Translate.LanguageDetectionTiming", timing,
+ LANGUAGE_DETECTION_TIMING_MAX_VALUE);
+
+ // Note on performance: Under normal circumstances, this should get called
+ // once per page load. The code will either manage to do it ON_TIME or will
+ // be DEFERRED until CLD is ready. In the latter case, CLD is in dynamic mode
+ // and may eventually become available, triggering the RESUMED event; after
+ // this, everything should start being ON_TIME. This should never run more
+ // than twice in a page load, under any conditions.
+ // Also note that language detection is triggered off of a delay AFTER the
+ // page load completed event has fired, making this very much off the critical
+ // path.
+ content::RenderThread::Get()->UpdateHistograms(
+ content::kHistogramSynchronizerReservedSequenceNumber);
+}
+
+} // namespace translate
diff --git a/components/translate/content/renderer/translate_helper.h b/components/translate/content/renderer/translate_helper.h
new file mode 100644
index 0000000..cde0efb
--- /dev/null
+++ b/components/translate/content/renderer/translate_helper.h
@@ -0,0 +1,284 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_TRANSLATE_CONTENT_RENDERER_TRANSLATE_HELPER_H_
+#define COMPONENTS_TRANSLATE_CONTENT_RENDERER_TRANSLATE_HELPER_H_
+
+#include <string>
+
+#include "base/gtest_prod_util.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/strings/string16.h"
+#include "base/time/time.h"
+#include "components/translate/content/renderer/renderer_cld_data_provider.h"
+#include "components/translate/core/common/translate_errors.h"
+#include "content/public/renderer/render_view_observer.h"
+#include "url/gurl.h"
+
+namespace blink {
+class WebDocument;
+class WebFrame;
+}
+
+namespace content {
+class RendererCldDataProvider;
+}
+
+namespace translate {
+
+// This class deals with page translation.
+// There is one TranslateHelper per RenderView.
+//
+// This class provides metrics that allow tracking the user experience impact
+// of non-static CldDataProvider implementations. For background on the data
+// providers, please refer to the following documentation:
+// http://www.chromium.org/developers/how-tos/compact-language-detector-cld-data-source-configuration
+//
+// Available metrics (from the LanguageDetectionTiming enum):
+// 1. ON_TIME
+// Recorded if PageCaptured(...) is invoked after CLD is available. This is
+// the ideal case, indicating that CLD is available before it is needed.
+// 2. DEFERRED
+// Recorded if PageCaptured(...) is invoked before CLD is available.
+// Sub-optimal case indicating that CLD wasn't available when it was needed,
+// so the request for detection has been deferred until CLD is available or
+// until the user navigates to a different page.
+// 3. RESUMED
+// Recorded if CLD becomes available after a language detection request was
+// deferred, but before the user navigated to a different page. Language
+// detection is ultimately completed, it just didn't happen on time.
+//
+// Note that there is NOT a metric that records the number of times that
+// language detection had to be aborted because CLD never became available in
+// time. This is because there is no reasonable way to cover all the cases
+// under which this could occur, particularly the destruction of the renderer
+// for which this object was created. However, this value can be synthetically
+// derived, using the logic below.
+//
+// Every page load that triggers language detection will result in the
+// recording of exactly one of the first two events: ON_TIME or DEFERRED. If
+// CLD is available in time to satisfy the request, the third event (RESUMED)
+// will be recorded; thus, the number of times when language detection
+// ultimately fails because CLD isn't ever available is implied as the number of
+// times that detection is deferred minus the number of times that language
+// detection is late:
+//
+// count(FAILED) ~= count(DEFERRED) - count(RESUMED)
+//
+// Note that this is not 100% accurate: some renderer process are so short-lived
+// that language detection wouldn't have been relevant anyway, and so a failure
+// to detect the language in a timely manner might be completely innocuous. The
+// overall problem with language detection is that it isn't possible to know
+// whether it was required or not until after it has been performed!
+//
+// We use histograms for recording these metrics. On Android, the renderer can
+// be killed without the chance to clean up or transmit these histograms,
+// leading to dropped metrics. To work around this, this method forces an IPC
+// message to be sent to the browser process immediately.
+class TranslateHelper : public content::RenderViewObserver {
+ public:
+ explicit TranslateHelper(content::RenderView* render_view,
+ int world_id,
+ int extension_group,
+ const std::string& extension_scheme);
+ virtual ~TranslateHelper();
+
+ // Informs us that the page's text has been extracted.
+ void PageCaptured(const base::string16& contents);
+
+ // Lets the translation system know that we are preparing to navigate to
+ // the specified URL. If there is anything that can or should be done before
+ // this URL loads, this is the time to prepare for it.
+ void PrepareForUrl(const GURL& url);
+
+ protected:
+ // The following methods are protected so they can be overridden in
+ // unit-tests.
+ void OnTranslatePage(int page_seq_no,
+ const std::string& translate_script,
+ const std::string& source_lang,
+ const std::string& target_lang);
+ void OnRevertTranslation(int page_seq_no);
+
+ // Returns true if the translate library is available, meaning the JavaScript
+ // has already been injected in that page.
+ virtual bool IsTranslateLibAvailable();
+
+ // Returns true if the translate library has been initialized successfully.
+ virtual bool IsTranslateLibReady();
+
+ // Returns true if the translation script has finished translating the page.
+ virtual bool HasTranslationFinished();
+
+ // Returns true if the translation script has reported an error performing the
+ // translation.
+ virtual bool HasTranslationFailed();
+
+ // Starts the translation by calling the translate library. This method
+ // should only be called when the translate script has been injected in the
+ // page. Returns false if the call failed immediately.
+ virtual bool StartTranslation();
+
+ // Asks the Translate element in the page what the language of the page is.
+ // Can only be called if a translation has happened and was successful.
+ // Returns the language code on success, an empty string on failure.
+ virtual std::string GetOriginalPageLanguage();
+
+ // Adjusts a delay time for a posted task. This is used in tests to do tasks
+ // immediately by returning 0.
+ virtual base::TimeDelta AdjustDelay(int delayInMs);
+
+ // Executes the JavaScript code in |script| in the main frame of RenderView.
+ virtual void ExecuteScript(const std::string& script);
+
+ // Executes the JavaScript code in |script| in the main frame of RenderView,
+ // and returns the boolean returned by the script evaluation if the script was
+ // run successfully. Otherwise, returns |fallback| value.
+ virtual bool ExecuteScriptAndGetBoolResult(const std::string& script,
+ bool fallback);
+
+ // Executes the JavaScript code in |script| in the main frame of RenderView,
+ // and returns the string returned by the script evaluation if the script was
+ // run successfully. Otherwise, returns empty string.
+ virtual std::string ExecuteScriptAndGetStringResult(
+ const std::string& script);
+
+ // Executes the JavaScript code in |script| in the main frame of RenderView.
+ // and returns the number returned by the script evaluation if the script was
+ // run successfully. Otherwise, returns 0.0.
+ virtual double ExecuteScriptAndGetDoubleResult(const std::string& script);
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(TranslateHelperTest, AdoptHtmlLang);
+ FRIEND_TEST_ALL_PREFIXES(TranslateHelperTest,
+ CLDAgreeWithLanguageCodeHavingCountryCode);
+ FRIEND_TEST_ALL_PREFIXES(TranslateHelperTest,
+ CLDDisagreeWithWrongLanguageCode);
+ FRIEND_TEST_ALL_PREFIXES(TranslateHelperTest,
+ InvalidLanguageMetaTagProviding);
+ FRIEND_TEST_ALL_PREFIXES(TranslateHelperTest, LanguageCodeTypoCorrection);
+ FRIEND_TEST_ALL_PREFIXES(TranslateHelperTest, LanguageCodeSynonyms);
+ FRIEND_TEST_ALL_PREFIXES(TranslateHelperTest, ResetInvalidLanguageCode);
+ FRIEND_TEST_ALL_PREFIXES(TranslateHelperTest, SimilarLanguageCode);
+ FRIEND_TEST_ALL_PREFIXES(TranslateHelperTest, WellKnownWrongConfiguration);
+
+ enum LanguageDetectionTiming {
+ ON_TIME, // Language detection was performed as soon as it was requested
+ DEFERRED, // Language detection couldn't be performed when it was requested
+ RESUMED, // A deferred language detection attempt was completed later
+ LANGUAGE_DETECTION_TIMING_MAX_VALUE // The bounding value for this enum
+ };
+
+ // Converts language code to the one used in server supporting list.
+ static void ConvertLanguageCodeSynonym(std::string* code);
+
+ // Returns whether the page associated with |document| is a candidate for
+ // translation. Some pages can explictly specify (via a meta-tag) that they
+ // should not be translated.
+ static bool IsTranslationAllowed(blink::WebDocument* document);
+
+ // RenderViewObserver implementation.
+ virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE;
+
+ // Informs us that the page's text has been extracted.
+ void PageCapturedImpl(int page_seq_no, const base::string16& contents);
+
+ // Cancels any translation that is currently being performed. This does not
+ // revert existing translations.
+ void CancelPendingTranslation();
+
+ // Checks if the current running page translation is finished or errored and
+ // notifies the browser accordingly. If the translation has not terminated,
+ // posts a task to check again later.
+ void CheckTranslateStatus(int page_seq_no);
+
+ // Called by TranslatePage to do the actual translation. |count| is used to
+ // limit the number of retries.
+ void TranslatePageImpl(int page_seq_no, int count);
+
+ // Sends a message to the browser to notify it that the translation failed
+ // with |error|.
+ void NotifyBrowserTranslationFailed(TranslateErrors::Type error);
+
+ // Convenience method to access the main frame. Can return NULL, typically
+ // if the page is being closed.
+ blink::WebFrame* GetMainFrame();
+
+ // Do not ask for CLD data any more.
+ void CancelCldDataPolling();
+
+ // Invoked when PageCaptured is called prior to obtaining CLD data. This
+ // method stores the page ID into deferred_page_id_ and COPIES the contents
+ // of the page, then sets deferred_page_capture_ to true. When CLD data is
+ // eventually received (in OnCldDataAvailable), any deferred request will be
+ // "resurrected" and allowed to proceed automatically, assuming that the
+ // page ID has not changed.
+ void DeferPageCaptured(const int page_id, const base::string16& contents);
+
+ // Start polling for CLD data.
+ // Polling will automatically halt as soon as the renderer obtains a
+ // reference to the data file.
+ void SendCldDataRequest(const int delay_millis, const int next_delay_millis);
+
+ // Callback triggered when CLD data becomes available.
+ void OnCldDataAvailable();
+
+ // Record the timing of language detection, immediately sending an IPC-based
+ // histogram delta update to the browser process in case the hosting renderer
+ // process terminates before the metrics would otherwise be transferred.
+ void RecordLanguageDetectionTiming(LanguageDetectionTiming timing);
+
+ // An ever-increasing sequence number of the current page, used to match up
+ // translation requests with responses.
+ int page_seq_no_;
+
+ // The states associated with the current translation.
+ bool translation_pending_;
+ std::string source_lang_;
+ std::string target_lang_;
+
+ // Time when a page langauge is determined. This is used to know a duration
+ // time from showing infobar to requesting translation.
+ base::TimeTicks language_determined_time_;
+
+ // Provides CLD data for this process.
+ scoped_ptr<RendererCldDataProvider> cld_data_provider_;
+
+ // Whether or not polling for CLD2 data has started.
+ bool cld_data_polling_started_;
+
+ // Whether or not CancelCldDataPolling has been called.
+ bool cld_data_polling_canceled_;
+
+ // Whether or not a PageCaptured event arrived prior to CLD data becoming
+ // available. If true, deferred_contents_ contains the most recent contents.
+ bool deferred_page_capture_;
+
+ // The ID of the page most recently reported to PageCaptured if
+ // deferred_page_capture_ is true.
+ int deferred_page_seq_no_;
+
+ // The world ID to use for script execution.
+ int world_id_;
+
+ // The extension group.
+ int extension_group_;
+
+ // The URL scheme for translate extensions.
+ std::string extension_scheme_;
+
+ // The contents of the page most recently reported to PageCaptured if
+ // deferred_page_capture_ is true.
+ base::string16 deferred_contents_;
+
+ // Method factory used to make calls to TranslatePageImpl.
+ base::WeakPtrFactory<TranslateHelper> weak_method_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(TranslateHelper);
+};
+
+} // namespace translate
+
+#endif // COMPONENTS_TRANSLATE_CONTENT_RENDERER_TRANSLATE_HELPER_H_