diff options
author | droger <droger@chromium.org> | 2014-09-17 07:54:05 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2014-09-17 14:54:15 +0000 |
commit | f1da1804fb44d2c36fe10d11808c1cfc0c369fcf (patch) | |
tree | aa8331e375be892fc299e3ce8f647a0ba7a60c74 /components/translate | |
parent | 2a3abf4b24bce7692cf41f0092934b6e48a19dee (diff) | |
download | chromium_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.gn | 9 | ||||
-rw-r--r-- | components/translate/content/renderer/DEPS | 2 | ||||
-rw-r--r-- | components/translate/content/renderer/translate_helper.cc | 638 | ||||
-rw-r--r-- | components/translate/content/renderer/translate_helper.h | 284 |
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_ |