NaCl: Update revision in DEPS, r12770 -> r12773
[chromium-blink-merge.git] / chrome / renderer / translate / translate_helper.cc
blobccd2279a294de5603fb918002707f27b268f5817
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/renderer/translate/translate_helper.h"
7 #include "base/bind.h"
8 #include "base/compiler_specific.h"
9 #include "base/logging.h"
10 #include "base/message_loop/message_loop.h"
11 #include "base/strings/string16.h"
12 #include "base/strings/string_util.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "chrome/common/render_messages.h"
15 #include "chrome/renderer/extensions/extension_groups.h"
16 #include "chrome/renderer/isolated_world_ids.h"
17 #include "components/translate/core/common/translate_constants.h"
18 #include "components/translate/core/common/translate_metrics.h"
19 #include "components/translate/core/common/translate_util.h"
20 #include "components/translate/language_detection/language_detection_util.h"
21 #include "content/public/renderer/render_view.h"
22 #include "third_party/WebKit/public/web/WebDocument.h"
23 #include "third_party/WebKit/public/web/WebElement.h"
24 #include "third_party/WebKit/public/web/WebFrame.h"
25 #include "third_party/WebKit/public/web/WebNode.h"
26 #include "third_party/WebKit/public/web/WebNodeList.h"
27 #include "third_party/WebKit/public/web/WebScriptSource.h"
28 #include "third_party/WebKit/public/web/WebView.h"
29 #include "third_party/WebKit/public/web/WebWidget.h"
30 #include "url/gurl.h"
31 #include "v8/include/v8.h"
33 using base::ASCIIToUTF16;
34 using blink::WebDocument;
35 using blink::WebElement;
36 using blink::WebFrame;
37 using blink::WebNode;
38 using blink::WebNodeList;
39 using blink::WebScriptSource;
40 using blink::WebSecurityOrigin;
41 using blink::WebString;
42 using blink::WebVector;
43 using blink::WebView;
45 namespace {
47 // The delay in milliseconds that we'll wait before checking to see if the
48 // translate library injected in the page is ready.
49 const int kTranslateInitCheckDelayMs = 150;
51 // The maximum number of times we'll check to see if the translate library
52 // injected in the page is ready.
53 const int kMaxTranslateInitCheckAttempts = 5;
55 // The delay we wait in milliseconds before checking whether the translation has
56 // finished.
57 const int kTranslateStatusCheckDelayMs = 400;
59 // Language name passed to the Translate element for it to detect the language.
60 const char kAutoDetectionLanguage[] = "auto";
62 // Isolated world sets following content-security-policy.
63 const char kContentSecurityPolicy[] = "script-src 'self' 'unsafe-eval'";
65 } // namespace
67 ////////////////////////////////////////////////////////////////////////////////
68 // TranslateHelper, public:
70 TranslateHelper::TranslateHelper(content::RenderView* render_view)
71 : content::RenderViewObserver(render_view),
72 page_id_(-1),
73 translation_pending_(false),
74 weak_method_factory_(this) {
77 TranslateHelper::~TranslateHelper() {
78 CancelPendingTranslation();
81 void TranslateHelper::PageCaptured(int page_id,
82 const base::string16& contents) {
83 // Get the document language as set by WebKit from the http-equiv
84 // meta tag for "content-language". This may or may not also
85 // have a value derived from the actual Content-Language HTTP
86 // header. The two actually have different meanings (despite the
87 // original intent of http-equiv to be an equivalent) with the former
88 // being the language of the document and the latter being the
89 // language of the intended audience (a distinction really only
90 // relevant for things like langauge textbooks). This distinction
91 // shouldn't affect translation.
92 WebFrame* main_frame = GetMainFrame();
93 if (!main_frame || render_view()->GetPageId() != page_id)
94 return;
95 page_id_ = page_id;
96 WebDocument document = main_frame->document();
97 std::string content_language = document.contentLanguage().utf8();
98 WebElement html_element = document.documentElement();
99 std::string html_lang;
100 // |html_element| can be null element, e.g. in
101 // BrowserTest.WindowOpenClose.
102 if (!html_element.isNull())
103 html_lang = html_element.getAttribute("lang").utf8();
104 std::string cld_language;
105 bool is_cld_reliable;
106 std::string language = translate::DeterminePageLanguage(
107 content_language, html_lang, contents, &cld_language, &is_cld_reliable);
109 if (language.empty())
110 return;
112 language_determined_time_ = base::TimeTicks::Now();
114 GURL url(document.url());
115 LanguageDetectionDetails details;
116 details.time = base::Time::Now();
117 details.url = url;
118 details.content_language = content_language;
119 details.cld_language = cld_language;
120 details.is_cld_reliable = is_cld_reliable;
121 details.html_root_language = html_lang;
122 details.adopted_language = language;
124 // TODO(hajimehoshi): If this affects performance, it should be set only if
125 // translate-internals tab exists.
126 details.contents = contents;
128 Send(new ChromeViewHostMsg_TranslateLanguageDetermined(
129 routing_id(),
130 details,
131 IsTranslationAllowed(&document) && !language.empty()));
134 void TranslateHelper::CancelPendingTranslation() {
135 weak_method_factory_.InvalidateWeakPtrs();
136 translation_pending_ = false;
137 source_lang_.clear();
138 target_lang_.clear();
141 ////////////////////////////////////////////////////////////////////////////////
142 // TranslateHelper, protected:
144 bool TranslateHelper::IsTranslateLibAvailable() {
145 return ExecuteScriptAndGetBoolResult(
146 "typeof cr != 'undefined' && typeof cr.googleTranslate != 'undefined' && "
147 "typeof cr.googleTranslate.translate == 'function'", false);
150 bool TranslateHelper::IsTranslateLibReady() {
151 return ExecuteScriptAndGetBoolResult("cr.googleTranslate.libReady", false);
154 bool TranslateHelper::HasTranslationFinished() {
155 return ExecuteScriptAndGetBoolResult("cr.googleTranslate.finished", true);
158 bool TranslateHelper::HasTranslationFailed() {
159 return ExecuteScriptAndGetBoolResult("cr.googleTranslate.error", true);
162 bool TranslateHelper::StartTranslation() {
163 std::string script = "cr.googleTranslate.translate('" +
164 source_lang_ +
165 "','" +
166 target_lang_ +
167 "')";
168 return ExecuteScriptAndGetBoolResult(script, false);
171 std::string TranslateHelper::GetOriginalPageLanguage() {
172 return ExecuteScriptAndGetStringResult("cr.googleTranslate.sourceLang");
175 base::TimeDelta TranslateHelper::AdjustDelay(int delayInMs) {
176 // Just converts |delayInMs| without any modification in practical cases.
177 // Tests will override this function to return modified value.
178 return base::TimeDelta::FromMilliseconds(delayInMs);
181 void TranslateHelper::ExecuteScript(const std::string& script) {
182 WebFrame* main_frame = GetMainFrame();
183 if (!main_frame)
184 return;
186 WebScriptSource source = WebScriptSource(ASCIIToUTF16(script));
187 main_frame->executeScriptInIsolatedWorld(
188 chrome::ISOLATED_WORLD_ID_TRANSLATE,
189 &source,
191 extensions::EXTENSION_GROUP_INTERNAL_TRANSLATE_SCRIPTS);
194 bool TranslateHelper::ExecuteScriptAndGetBoolResult(const std::string& script,
195 bool fallback) {
196 WebFrame* main_frame = GetMainFrame();
197 if (!main_frame)
198 return fallback;
200 v8::HandleScope handle_scope(v8::Isolate::GetCurrent());
201 WebVector<v8::Local<v8::Value> > results;
202 WebScriptSource source = WebScriptSource(ASCIIToUTF16(script));
203 main_frame->executeScriptInIsolatedWorld(
204 chrome::ISOLATED_WORLD_ID_TRANSLATE,
205 &source,
207 extensions::EXTENSION_GROUP_INTERNAL_TRANSLATE_SCRIPTS,
208 &results);
209 if (results.size() != 1 || results[0].IsEmpty() || !results[0]->IsBoolean()) {
210 NOTREACHED();
211 return fallback;
214 return results[0]->BooleanValue();
217 std::string TranslateHelper::ExecuteScriptAndGetStringResult(
218 const std::string& script) {
219 WebFrame* main_frame = GetMainFrame();
220 if (!main_frame)
221 return std::string();
223 v8::HandleScope handle_scope(v8::Isolate::GetCurrent());
224 WebVector<v8::Local<v8::Value> > results;
225 WebScriptSource source = WebScriptSource(ASCIIToUTF16(script));
226 main_frame->executeScriptInIsolatedWorld(
227 chrome::ISOLATED_WORLD_ID_TRANSLATE,
228 &source,
230 extensions::EXTENSION_GROUP_INTERNAL_TRANSLATE_SCRIPTS,
231 &results);
232 if (results.size() != 1 || results[0].IsEmpty() || !results[0]->IsString()) {
233 NOTREACHED();
234 return std::string();
237 v8::Local<v8::String> v8_str = results[0]->ToString();
238 int length = v8_str->Utf8Length() + 1;
239 scoped_ptr<char[]> str(new char[length]);
240 v8_str->WriteUtf8(str.get(), length);
241 return std::string(str.get());
244 double TranslateHelper::ExecuteScriptAndGetDoubleResult(
245 const std::string& script) {
246 WebFrame* main_frame = GetMainFrame();
247 if (!main_frame)
248 return 0.0;
250 v8::HandleScope handle_scope(v8::Isolate::GetCurrent());
251 WebVector<v8::Local<v8::Value> > results;
252 WebScriptSource source = WebScriptSource(ASCIIToUTF16(script));
253 main_frame->executeScriptInIsolatedWorld(
254 chrome::ISOLATED_WORLD_ID_TRANSLATE,
255 &source,
257 extensions::EXTENSION_GROUP_INTERNAL_TRANSLATE_SCRIPTS,
258 &results);
259 if (results.size() != 1 || results[0].IsEmpty() || !results[0]->IsNumber()) {
260 NOTREACHED();
261 return 0.0;
264 return results[0]->NumberValue();
267 ////////////////////////////////////////////////////////////////////////////////
268 // TranslateHelper, private:
271 // static
272 bool TranslateHelper::IsTranslationAllowed(WebDocument* document) {
273 WebElement head = document->head();
274 if (head.isNull() || !head.hasChildNodes())
275 return true;
277 const WebString meta(ASCIIToUTF16("meta"));
278 const WebString name(ASCIIToUTF16("name"));
279 const WebString google(ASCIIToUTF16("google"));
280 const WebString value(ASCIIToUTF16("value"));
281 const WebString content(ASCIIToUTF16("content"));
283 WebNodeList children = head.childNodes();
284 for (size_t i = 0; i < children.length(); ++i) {
285 WebNode node = children.item(i);
286 if (!node.isElementNode())
287 continue;
288 WebElement element = node.to<WebElement>();
289 // Check if a tag is <meta>.
290 if (!element.hasTagName(meta))
291 continue;
292 // Check if the tag contains name="google".
293 WebString attribute = element.getAttribute(name);
294 if (attribute.isNull() || attribute != google)
295 continue;
296 // Check if the tag contains value="notranslate", or content="notranslate".
297 attribute = element.getAttribute(value);
298 if (attribute.isNull())
299 attribute = element.getAttribute(content);
300 if (attribute.isNull())
301 continue;
302 if (LowerCaseEqualsASCII(attribute, "notranslate"))
303 return false;
305 return true;
308 bool TranslateHelper::OnMessageReceived(const IPC::Message& message) {
309 bool handled = true;
310 IPC_BEGIN_MESSAGE_MAP(TranslateHelper, message)
311 IPC_MESSAGE_HANDLER(ChromeViewMsg_TranslatePage, OnTranslatePage)
312 IPC_MESSAGE_HANDLER(ChromeViewMsg_RevertTranslation, OnRevertTranslation)
313 IPC_MESSAGE_UNHANDLED(handled = false)
314 IPC_END_MESSAGE_MAP()
315 return handled;
318 void TranslateHelper::OnTranslatePage(int page_id,
319 const std::string& translate_script,
320 const std::string& source_lang,
321 const std::string& target_lang) {
322 WebFrame* main_frame = GetMainFrame();
323 if (!main_frame ||
324 page_id_ != page_id ||
325 render_view()->GetPageId() != page_id)
326 return; // We navigated away, nothing to do.
328 // A similar translation is already under way, nothing to do.
329 if (translation_pending_ && target_lang_ == target_lang)
330 return;
332 // Any pending translation is now irrelevant.
333 CancelPendingTranslation();
335 // Set our states.
336 translation_pending_ = true;
338 // If the source language is undetermined, we'll let the translate element
339 // detect it.
340 source_lang_ = (source_lang != translate::kUnknownLanguageCode) ?
341 source_lang : kAutoDetectionLanguage;
342 target_lang_ = target_lang;
344 translate::ReportUserActionDuration(language_determined_time_,
345 base::TimeTicks::Now());
347 GURL url(main_frame->document().url());
348 translate::ReportPageScheme(url.scheme());
350 // Set up v8 isolated world with proper content-security-policy and
351 // security-origin.
352 WebFrame* frame = GetMainFrame();
353 if (frame) {
354 frame->setIsolatedWorldContentSecurityPolicy(
355 chrome::ISOLATED_WORLD_ID_TRANSLATE,
356 WebString::fromUTF8(kContentSecurityPolicy));
358 GURL security_origin = translate::GetTranslateSecurityOrigin();
359 frame->setIsolatedWorldSecurityOrigin(
360 chrome::ISOLATED_WORLD_ID_TRANSLATE,
361 WebSecurityOrigin::create(security_origin));
364 if (!IsTranslateLibAvailable()) {
365 // Evaluate the script to add the translation related method to the global
366 // context of the page.
367 ExecuteScript(translate_script);
368 DCHECK(IsTranslateLibAvailable());
371 TranslatePageImpl(0);
374 void TranslateHelper::OnRevertTranslation(int page_id) {
375 if (page_id_ != page_id || render_view()->GetPageId() != page_id)
376 return; // We navigated away, nothing to do.
378 if (!IsTranslateLibAvailable()) {
379 NOTREACHED();
380 return;
383 CancelPendingTranslation();
385 ExecuteScript("cr.googleTranslate.revert()");
388 void TranslateHelper::CheckTranslateStatus() {
389 // If this is not the same page, the translation has been canceled. If the
390 // view is gone, the page is closing.
391 if (page_id_ != render_view()->GetPageId() || !render_view()->GetWebView())
392 return;
394 // First check if there was an error.
395 if (HasTranslationFailed()) {
396 // TODO(toyoshim): Check |errorCode| of translate.js and notify it here.
397 NotifyBrowserTranslationFailed(TranslateErrors::TRANSLATION_ERROR);
398 return; // There was an error.
401 if (HasTranslationFinished()) {
402 std::string actual_source_lang;
403 // Translation was successfull, if it was auto, retrieve the source
404 // language the Translate Element detected.
405 if (source_lang_ == kAutoDetectionLanguage) {
406 actual_source_lang = GetOriginalPageLanguage();
407 if (actual_source_lang.empty()) {
408 NotifyBrowserTranslationFailed(TranslateErrors::UNKNOWN_LANGUAGE);
409 return;
410 } else if (actual_source_lang == target_lang_) {
411 NotifyBrowserTranslationFailed(TranslateErrors::IDENTICAL_LANGUAGES);
412 return;
414 } else {
415 actual_source_lang = source_lang_;
418 if (!translation_pending_) {
419 NOTREACHED();
420 return;
423 translation_pending_ = false;
425 // Check JavaScript performance counters for UMA reports.
426 translate::ReportTimeToTranslate(
427 ExecuteScriptAndGetDoubleResult("cr.googleTranslate.translationTime"));
429 // Notify the browser we are done.
430 render_view()->Send(new ChromeViewHostMsg_PageTranslated(
431 render_view()->GetRoutingID(), render_view()->GetPageId(),
432 actual_source_lang, target_lang_, TranslateErrors::NONE));
433 return;
436 // The translation is still pending, check again later.
437 base::MessageLoop::current()->PostDelayedTask(
438 FROM_HERE,
439 base::Bind(&TranslateHelper::CheckTranslateStatus,
440 weak_method_factory_.GetWeakPtr()),
441 AdjustDelay(kTranslateStatusCheckDelayMs));
444 void TranslateHelper::TranslatePageImpl(int count) {
445 DCHECK_LT(count, kMaxTranslateInitCheckAttempts);
446 if (page_id_ != render_view()->GetPageId() || !render_view()->GetWebView())
447 return;
449 if (!IsTranslateLibReady()) {
450 // The library is not ready, try again later, unless we have tried several
451 // times unsucessfully already.
452 if (++count >= kMaxTranslateInitCheckAttempts) {
453 NotifyBrowserTranslationFailed(TranslateErrors::INITIALIZATION_ERROR);
454 return;
456 base::MessageLoop::current()->PostDelayedTask(
457 FROM_HERE,
458 base::Bind(&TranslateHelper::TranslatePageImpl,
459 weak_method_factory_.GetWeakPtr(),
460 count),
461 AdjustDelay(count * kTranslateInitCheckDelayMs));
462 return;
465 // The library is loaded, and ready for translation now.
466 // Check JavaScript performance counters for UMA reports.
467 translate::ReportTimeToBeReady(
468 ExecuteScriptAndGetDoubleResult("cr.googleTranslate.readyTime"));
469 translate::ReportTimeToLoad(
470 ExecuteScriptAndGetDoubleResult("cr.googleTranslate.loadTime"));
472 if (!StartTranslation()) {
473 NotifyBrowserTranslationFailed(TranslateErrors::TRANSLATION_ERROR);
474 return;
476 // Check the status of the translation.
477 base::MessageLoop::current()->PostDelayedTask(
478 FROM_HERE,
479 base::Bind(&TranslateHelper::CheckTranslateStatus,
480 weak_method_factory_.GetWeakPtr()),
481 AdjustDelay(kTranslateStatusCheckDelayMs));
484 void TranslateHelper::NotifyBrowserTranslationFailed(
485 TranslateErrors::Type error) {
486 translation_pending_ = false;
487 // Notify the browser there was an error.
488 render_view()->Send(new ChromeViewHostMsg_PageTranslated(
489 render_view()->GetRoutingID(), page_id_, source_lang_,
490 target_lang_, error));
493 WebFrame* TranslateHelper::GetMainFrame() {
494 WebView* web_view = render_view()->GetWebView();
496 // When the tab is going to be closed, the web_view can be NULL.
497 if (!web_view)
498 return NULL;
500 return web_view->mainFrame();