This source file includes following definitions.
- deferred_contents_
- PrepareForUrl
- DeferPageCaptured
- PageCaptured
- CancelPendingTranslation
- IsTranslateLibAvailable
- IsTranslateLibReady
- HasTranslationFinished
- HasTranslationFailed
- StartTranslation
- GetOriginalPageLanguage
- AdjustDelay
- ExecuteScript
- ExecuteScriptAndGetBoolResult
- ExecuteScriptAndGetStringResult
- ExecuteScriptAndGetDoubleResult
- IsTranslationAllowed
- OnMessageReceived
- OnTranslatePage
- OnRevertTranslation
- CheckTranslateStatus
- TranslatePageImpl
- NotifyBrowserTranslationFailed
- GetMainFrame
- CancelCLD2DataFilePolling
- SendCLD2DataFileRequest
- OnCLDDataAvailable
- LoadCLDDData
#include "chrome/renderer/translate/translate_helper.h"
#if defined(CLD2_DYNAMIC_MODE)
#include <stdint.h>
#endif
#include "base/bind.h"
#include "base/compiler_specific.h"
#if defined(CLD2_DYNAMIC_MODE)
#include "base/files/memory_mapped_file.h"
#endif
#include "base/logging.h"
#include "base/message_loop/message_loop.h"
#include "base/strings/string16.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/renderer/extensions/extension_groups.h"
#include "chrome/renderer/isolated_world_ids.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/language_detection/language_detection_util.h"
#include "content/public/renderer/render_view.h"
#include "extensions/common/constants.h"
#include "ipc/ipc_platform_file.h"
#if defined(CLD2_DYNAMIC_MODE)
#include "content/public/common/url_constants.h"
#include "third_party/cld_2/src/public/compact_lang_det.h"
#endif
#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 {
const int kTranslateInitCheckDelayMs = 150;
const int kMaxTranslateInitCheckAttempts = 5;
const int kTranslateStatusCheckDelayMs = 400;
const char kAutoDetectionLanguage[] = "auto";
const char kContentSecurityPolicy[] = "script-src 'self' 'unsafe-eval'";
}
#if defined(CLD2_DYNAMIC_MODE)
base::LazyInstance<TranslateHelper::CLDMmapWrapper>::Leaky
TranslateHelper::s_cld_mmap_ = LAZY_INSTANCE_INITIALIZER;
#endif
TranslateHelper::TranslateHelper(content::RenderView* render_view)
: content::RenderViewObserver(render_view),
page_id_(-1),
translation_pending_(false),
weak_method_factory_(this)
#if defined(CLD2_DYNAMIC_MODE)
,cld2_data_file_polling_started_(false),
cld2_data_file_polling_canceled_(false),
deferred_page_capture_(false),
deferred_page_id_(-1),
deferred_contents_(ASCIIToUTF16(""))
#endif
{
}
TranslateHelper::~TranslateHelper() {
CancelPendingTranslation();
#if defined(CLD2_DYNAMIC_MODE)
CancelCLD2DataFilePolling();
#endif
}
void TranslateHelper::PrepareForUrl(const GURL& url) {
#if defined(CLD2_DYNAMIC_MODE)
deferred_page_capture_ = false;
deferred_contents_.clear();
if (cld2_data_file_polling_started_)
return;
if (url.is_empty())
return;
if (url.SchemeIs(content::kChromeUIScheme))
return;
if (url.SchemeIs(content::kChromeDevToolsScheme))
return;
if (url.SchemeIs(content::kFtpScheme))
return;
#if defined(OS_CHROMEOS)
if (url.SchemeIs(extensions::kExtensionScheme) &&
url.DomainIs(file_manager::kFileManagerAppId))
return;
#endif
cld2_data_file_polling_started_ = true;
TranslateHelper::SendCLD2DataFileRequest(0, 1000);
#endif
}
#if defined(CLD2_DYNAMIC_MODE)
void TranslateHelper::DeferPageCaptured(const int page_id,
const base::string16& contents) {
deferred_page_capture_ = true;
deferred_page_id_ = page_id;
deferred_contents_ = contents;
}
#endif
void TranslateHelper::PageCaptured(int page_id,
const base::string16& contents) {
WebFrame* main_frame = GetMainFrame();
if (!main_frame || render_view()->GetPageId() != page_id)
return;
#if defined(CLD2_DYNAMIC_MODE)
if (!CLD2::isDataLoaded()) {
TranslateHelper::DeferPageCaptured(page_id, contents);
return;
}
#endif
page_id_ = page_id;
WebDocument document = main_frame->document();
std::string content_language = document.contentLanguage().utf8();
WebElement html_element = document.documentElement();
std::string html_lang;
if (!html_element.isNull())
html_lang = html_element.getAttribute("lang").utf8();
std::string cld_language;
bool is_cld_reliable;
std::string language = translate::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;
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();
#if defined(CLD2_DYNAMIC_MODE)
CancelCLD2DataFilePolling();
#endif
}
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) {
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(
chrome::ISOLATED_WORLD_ID_TRANSLATE,
&source,
1,
extensions::EXTENSION_GROUP_INTERNAL_TRANSLATE_SCRIPTS);
}
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(
chrome::ISOLATED_WORLD_ID_TRANSLATE,
&source,
1,
extensions::EXTENSION_GROUP_INTERNAL_TRANSLATE_SCRIPTS,
&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(
chrome::ISOLATED_WORLD_ID_TRANSLATE,
&source,
1,
extensions::EXTENSION_GROUP_INTERNAL_TRANSLATE_SCRIPTS,
&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(
chrome::ISOLATED_WORLD_ID_TRANSLATE,
&source,
1,
extensions::EXTENSION_GROUP_INTERNAL_TRANSLATE_SCRIPTS,
&results);
if (results.size() != 1 || results[0].IsEmpty() || !results[0]->IsNumber()) {
NOTREACHED();
return 0.0;
}
return results[0]->NumberValue();
}
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>();
if (!element.hasTagName(meta))
continue;
WebString attribute = element.getAttribute(name);
if (attribute.isNull() || attribute != google)
continue;
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)
#if defined(CLD2_DYNAMIC_MODE)
IPC_MESSAGE_HANDLER(ChromeViewMsg_CLDDataAvailable, OnCLDDataAvailable);
#endif
IPC_MESSAGE_UNHANDLED(handled = false)
IPC_END_MESSAGE_MAP()
return handled;
}
void TranslateHelper::OnTranslatePage(int page_id,
const std::string& translate_script,
const std::string& source_lang,
const std::string& target_lang) {
WebFrame* main_frame = GetMainFrame();
if (!main_frame ||
page_id_ != page_id ||
render_view()->GetPageId() != page_id)
return;
if (translation_pending_ && target_lang_ == target_lang)
return;
CancelPendingTranslation();
translation_pending_ = true;
source_lang_ = (source_lang != translate::kUnknownLanguageCode) ?
source_lang : kAutoDetectionLanguage;
target_lang_ = target_lang;
translate::ReportUserActionDuration(language_determined_time_,
base::TimeTicks::Now());
GURL url(main_frame->document().url());
translate::ReportPageScheme(url.scheme());
WebFrame* frame = GetMainFrame();
if (frame) {
frame->setIsolatedWorldContentSecurityPolicy(
chrome::ISOLATED_WORLD_ID_TRANSLATE,
WebString::fromUTF8(kContentSecurityPolicy));
GURL security_origin = translate::GetTranslateSecurityOrigin();
frame->setIsolatedWorldSecurityOrigin(
chrome::ISOLATED_WORLD_ID_TRANSLATE,
WebSecurityOrigin::create(security_origin));
}
if (!IsTranslateLibAvailable()) {
ExecuteScript(translate_script);
DCHECK(IsTranslateLibAvailable());
}
TranslatePageImpl(0);
}
void TranslateHelper::OnRevertTranslation(int page_id) {
if (page_id_ != page_id || render_view()->GetPageId() != page_id)
return;
if (!IsTranslateLibAvailable()) {
NOTREACHED();
return;
}
CancelPendingTranslation();
ExecuteScript("cr.googleTranslate.revert()");
}
void TranslateHelper::CheckTranslateStatus() {
if (page_id_ != render_view()->GetPageId() || !render_view()->GetWebView())
return;
if (HasTranslationFailed()) {
NotifyBrowserTranslationFailed(TranslateErrors::TRANSLATION_ERROR);
return;
}
if (HasTranslationFinished()) {
std::string actual_source_lang;
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;
translate::ReportTimeToTranslate(
ExecuteScriptAndGetDoubleResult("cr.googleTranslate.translationTime"));
render_view()->Send(new ChromeViewHostMsg_PageTranslated(
render_view()->GetRoutingID(), render_view()->GetPageId(),
actual_source_lang, target_lang_, TranslateErrors::NONE));
return;
}
base::MessageLoop::current()->PostDelayedTask(
FROM_HERE,
base::Bind(&TranslateHelper::CheckTranslateStatus,
weak_method_factory_.GetWeakPtr()),
AdjustDelay(kTranslateStatusCheckDelayMs));
}
void TranslateHelper::TranslatePageImpl(int count) {
DCHECK_LT(count, kMaxTranslateInitCheckAttempts);
if (page_id_ != render_view()->GetPageId() || !render_view()->GetWebView())
return;
if (!IsTranslateLibReady()) {
if (++count >= kMaxTranslateInitCheckAttempts) {
NotifyBrowserTranslationFailed(TranslateErrors::INITIALIZATION_ERROR);
return;
}
base::MessageLoop::current()->PostDelayedTask(
FROM_HERE,
base::Bind(&TranslateHelper::TranslatePageImpl,
weak_method_factory_.GetWeakPtr(),
count),
AdjustDelay(count * kTranslateInitCheckDelayMs));
return;
}
translate::ReportTimeToBeReady(
ExecuteScriptAndGetDoubleResult("cr.googleTranslate.readyTime"));
translate::ReportTimeToLoad(
ExecuteScriptAndGetDoubleResult("cr.googleTranslate.loadTime"));
if (!StartTranslation()) {
NotifyBrowserTranslationFailed(TranslateErrors::TRANSLATION_ERROR);
return;
}
base::MessageLoop::current()->PostDelayedTask(
FROM_HERE,
base::Bind(&TranslateHelper::CheckTranslateStatus,
weak_method_factory_.GetWeakPtr()),
AdjustDelay(kTranslateStatusCheckDelayMs));
}
void TranslateHelper::NotifyBrowserTranslationFailed(
TranslateErrors::Type error) {
translation_pending_ = false;
render_view()->Send(new ChromeViewHostMsg_PageTranslated(
render_view()->GetRoutingID(), page_id_, source_lang_,
target_lang_, error));
}
WebFrame* TranslateHelper::GetMainFrame() {
WebView* web_view = render_view()->GetWebView();
if (!web_view)
return NULL;
return web_view->mainFrame();
}
#if defined(CLD2_DYNAMIC_MODE)
void TranslateHelper::CancelCLD2DataFilePolling() {
cld2_data_file_polling_canceled_ = true;
}
void TranslateHelper::SendCLD2DataFileRequest(const int delay_millis,
const int next_delay_millis) {
if (cld2_data_file_polling_canceled_)
return;
if (CLD2::isDataLoaded())
return;
Send(new ChromeViewHostMsg_NeedCLDData(routing_id()));
base::MessageLoop::current()->PostDelayedTask(
FROM_HERE,
base::Bind(&TranslateHelper::SendCLD2DataFileRequest,
weak_method_factory_.GetWeakPtr(),
next_delay_millis, next_delay_millis),
base::TimeDelta::FromMilliseconds(delay_millis));
}
void TranslateHelper::OnCLDDataAvailable(
const IPC::PlatformFileForTransit ipc_file_handle,
const uint64 data_offset,
const uint64 data_length) {
LoadCLDDData(ipc_file_handle, data_offset, data_length);
if (deferred_page_capture_ && CLD2::isDataLoaded()) {
deferred_page_capture_ = false;
PageCaptured(deferred_page_id_, deferred_contents_);
deferred_page_id_ = -1;
deferred_contents_.clear();
}
}
void TranslateHelper::LoadCLDDData(
const IPC::PlatformFileForTransit ipc_file_handle,
const uint64 data_offset,
const uint64 data_length) {
if (cld2_data_file_polling_canceled_)
return;
if (CLD2::isDataLoaded())
return;
base::PlatformFile platform_file =
IPC::PlatformFileForTransitToPlatformFile(ipc_file_handle);
if (platform_file == base::kInvalidPlatformFileValue) {
LOG(ERROR) << "Can't find the CLD data file.";
return;
}
base::File basic_file(platform_file);
s_cld_mmap_.Get().value = new base::MemoryMappedFile();
bool initialized = s_cld_mmap_.Get().value->Initialize(basic_file.Pass());
if (!initialized) {
LOG(ERROR) << "mmap initialization failed";
delete s_cld_mmap_.Get().value;
s_cld_mmap_.Get().value = NULL;
return;
}
uint64 max_int32 = std::numeric_limits<int32>::max();
if (data_length + data_offset > s_cld_mmap_.Get().value->length()
|| data_length > max_int32) {
LOG(ERROR) << "Illegal mmap config: data_offset="
<< data_offset << ", data_length=" << data_length
<< ", mmap->length()=" << s_cld_mmap_.Get().value->length();
delete s_cld_mmap_.Get().value;
s_cld_mmap_.Get().value = NULL;
return;
}
const uint8* data_ptr = s_cld_mmap_.Get().value->data() + data_offset;
CLD2::loadDataFromRawAddress(data_ptr, data_length);
DCHECK(CLD2::isDataLoaded()) << "Failed to load CLD data from mmap";
}
#endif