Home | History | Annotate | Download | only in translate
      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.
      4 
      5 #include "chrome/renderer/translate/translate_helper.h"
      6 
      7 #if defined(CLD2_DYNAMIC_MODE)
      8 #include <stdint.h>
      9 #endif
     10 
     11 #include "base/bind.h"
     12 #include "base/compiler_specific.h"
     13 #if defined(CLD2_DYNAMIC_MODE)
     14 #include "base/files/memory_mapped_file.h"
     15 #endif
     16 #include "base/logging.h"
     17 #include "base/message_loop/message_loop.h"
     18 #include "base/strings/string16.h"
     19 #include "base/strings/string_util.h"
     20 #include "base/strings/utf_string_conversions.h"
     21 #include "chrome/renderer/isolated_world_ids.h"
     22 #include "components/translate/content/common/translate_messages.h"
     23 #include "components/translate/core/common/translate_constants.h"
     24 #include "components/translate/core/common/translate_metrics.h"
     25 #include "components/translate/core/common/translate_util.h"
     26 #include "components/translate/core/language_detection/language_detection_util.h"
     27 #include "content/public/renderer/render_view.h"
     28 #include "extensions/common/constants.h"
     29 #include "extensions/renderer/extension_groups.h"
     30 #include "ipc/ipc_platform_file.h"
     31 #if defined(CLD2_DYNAMIC_MODE)
     32 #include "content/public/common/url_constants.h"
     33 #include "third_party/cld_2/src/public/compact_lang_det.h"
     34 #endif
     35 #include "third_party/WebKit/public/web/WebDocument.h"
     36 #include "third_party/WebKit/public/web/WebElement.h"
     37 #include "third_party/WebKit/public/web/WebFrame.h"
     38 #include "third_party/WebKit/public/web/WebNode.h"
     39 #include "third_party/WebKit/public/web/WebNodeList.h"
     40 #include "third_party/WebKit/public/web/WebScriptSource.h"
     41 #include "third_party/WebKit/public/web/WebView.h"
     42 #include "third_party/WebKit/public/web/WebWidget.h"
     43 #include "url/gurl.h"
     44 #include "v8/include/v8.h"
     45 
     46 using base::ASCIIToUTF16;
     47 using blink::WebDocument;
     48 using blink::WebElement;
     49 using blink::WebFrame;
     50 using blink::WebNode;
     51 using blink::WebNodeList;
     52 using blink::WebScriptSource;
     53 using blink::WebSecurityOrigin;
     54 using blink::WebString;
     55 using blink::WebVector;
     56 using blink::WebView;
     57 
     58 namespace {
     59 
     60 // The delay in milliseconds that we'll wait before checking to see if the
     61 // translate library injected in the page is ready.
     62 const int kTranslateInitCheckDelayMs = 150;
     63 
     64 // The maximum number of times we'll check to see if the translate library
     65 // injected in the page is ready.
     66 const int kMaxTranslateInitCheckAttempts = 5;
     67 
     68 // The delay we wait in milliseconds before checking whether the translation has
     69 // finished.
     70 const int kTranslateStatusCheckDelayMs = 400;
     71 
     72 // Language name passed to the Translate element for it to detect the language.
     73 const char kAutoDetectionLanguage[] = "auto";
     74 
     75 // Isolated world sets following content-security-policy.
     76 const char kContentSecurityPolicy[] = "script-src 'self' 'unsafe-eval'";
     77 
     78 }  // namespace
     79 
     80 #if defined(CLD2_DYNAMIC_MODE)
     81 // The mmap for the CLD2 data must be held forever once it is available in the
     82 // process. This is declared static in the translate_helper.h.
     83 base::LazyInstance<TranslateHelper::CLDMmapWrapper>::Leaky
     84   TranslateHelper::s_cld_mmap_ = LAZY_INSTANCE_INITIALIZER;
     85 #endif
     86 
     87 ////////////////////////////////////////////////////////////////////////////////
     88 // TranslateHelper, public:
     89 //
     90 TranslateHelper::TranslateHelper(content::RenderView* render_view)
     91     : content::RenderViewObserver(render_view),
     92       page_id_(-1),
     93       translation_pending_(false),
     94       weak_method_factory_(this)
     95 #if defined(CLD2_DYNAMIC_MODE)
     96       ,cld2_data_file_polling_started_(false),
     97       cld2_data_file_polling_canceled_(false),
     98       deferred_page_capture_(false),
     99       deferred_page_id_(-1),
    100       deferred_contents_(ASCIIToUTF16(""))
    101 #endif
    102   {
    103 }
    104 
    105 TranslateHelper::~TranslateHelper() {
    106   CancelPendingTranslation();
    107 #if defined(CLD2_DYNAMIC_MODE)
    108   CancelCLD2DataFilePolling();
    109 #endif
    110 }
    111 
    112 void TranslateHelper::PrepareForUrl(const GURL& url) {
    113 #if defined(CLD2_DYNAMIC_MODE)
    114   deferred_page_capture_ = false;
    115   deferred_contents_.clear();
    116   if (cld2_data_file_polling_started_)
    117     return;
    118 
    119   // TODO(andrewhayden): Refactor translate_manager.cc's IsTranslatableURL to
    120   // components/translate/core/common/translate_util.cc, and ignore any URL
    121   // that fails that check. This will require moving unit tests and rewiring
    122   // other function calls as well, so for now replicate the logic here.
    123   if (url.is_empty())
    124     return;
    125   if (url.SchemeIs(content::kChromeUIScheme))
    126     return;
    127   if (url.SchemeIs(content::kChromeDevToolsScheme))
    128     return;
    129   if (url.SchemeIs(url::kFtpScheme))
    130     return;
    131 #if defined(OS_CHROMEOS)
    132   if (url.SchemeIs(extensions::kExtensionScheme) &&
    133       url.DomainIs(file_manager::kFileManagerAppId))
    134     return;
    135 #endif
    136 
    137   // Start polling for CLD data.
    138   cld2_data_file_polling_started_ = true;
    139   TranslateHelper::SendCLD2DataFileRequest(0, 1000);
    140 #endif
    141 }
    142 
    143 #if defined(CLD2_DYNAMIC_MODE)
    144 void TranslateHelper::DeferPageCaptured(const int page_id,
    145                                         const base::string16& contents) {
    146   deferred_page_capture_ = true;
    147   deferred_page_id_ = page_id;
    148   deferred_contents_ = contents;
    149 }
    150 #endif
    151 
    152 void TranslateHelper::PageCaptured(int page_id,
    153                                    const base::string16& contents) {
    154   // Get the document language as set by WebKit from the http-equiv
    155   // meta tag for "content-language".  This may or may not also
    156   // have a value derived from the actual Content-Language HTTP
    157   // header.  The two actually have different meanings (despite the
    158   // original intent of http-equiv to be an equivalent) with the former
    159   // being the language of the document and the latter being the
    160   // language of the intended audience (a distinction really only
    161   // relevant for things like langauge textbooks).  This distinction
    162   // shouldn't affect translation.
    163   WebFrame* main_frame = GetMainFrame();
    164   if (!main_frame || render_view()->GetPageId() != page_id)
    165     return;
    166 
    167   // TODO(andrewhayden): UMA insertion point here: Track if data is available.
    168   // TODO(andrewhayden): Retry insertion point here, retry till data available.
    169 #if defined(CLD2_DYNAMIC_MODE)
    170   if (!CLD2::isDataLoaded()) {
    171     // We're in dynamic mode and CLD data isn't loaded. Retry when CLD data
    172     // is loaded, if ever.
    173     TranslateHelper::DeferPageCaptured(page_id, contents);
    174     return;
    175   }
    176 #endif
    177   page_id_ = page_id;
    178   WebDocument document = main_frame->document();
    179   std::string content_language = document.contentLanguage().utf8();
    180   WebElement html_element = document.documentElement();
    181   std::string html_lang;
    182   // |html_element| can be null element, e.g. in
    183   // BrowserTest.WindowOpenClose.
    184   if (!html_element.isNull())
    185     html_lang = html_element.getAttribute("lang").utf8();
    186   std::string cld_language;
    187   bool is_cld_reliable;
    188   std::string language = translate::DeterminePageLanguage(
    189       content_language, html_lang, contents, &cld_language, &is_cld_reliable);
    190 
    191   if (language.empty())
    192     return;
    193 
    194   language_determined_time_ = base::TimeTicks::Now();
    195 
    196   GURL url(document.url());
    197   LanguageDetectionDetails details;
    198   details.time = base::Time::Now();
    199   details.url = url;
    200   details.content_language = content_language;
    201   details.cld_language = cld_language;
    202   details.is_cld_reliable = is_cld_reliable;
    203   details.html_root_language = html_lang;
    204   details.adopted_language = language;
    205 
    206   // TODO(hajimehoshi): If this affects performance, it should be set only if
    207   // translate-internals tab exists.
    208   details.contents = contents;
    209 
    210   Send(new ChromeViewHostMsg_TranslateLanguageDetermined(
    211       routing_id(),
    212       details,
    213       IsTranslationAllowed(&document) && !language.empty()));
    214 }
    215 
    216 void TranslateHelper::CancelPendingTranslation() {
    217   weak_method_factory_.InvalidateWeakPtrs();
    218   translation_pending_ = false;
    219   source_lang_.clear();
    220   target_lang_.clear();
    221 #if defined(CLD2_DYNAMIC_MODE)
    222   CancelCLD2DataFilePolling();
    223 #endif
    224 }
    225 
    226 ////////////////////////////////////////////////////////////////////////////////
    227 // TranslateHelper, protected:
    228 //
    229 bool TranslateHelper::IsTranslateLibAvailable() {
    230   return ExecuteScriptAndGetBoolResult(
    231       "typeof cr != 'undefined' && typeof cr.googleTranslate != 'undefined' && "
    232       "typeof cr.googleTranslate.translate == 'function'", false);
    233 }
    234 
    235 bool TranslateHelper::IsTranslateLibReady() {
    236   return ExecuteScriptAndGetBoolResult("cr.googleTranslate.libReady", false);
    237 }
    238 
    239 bool TranslateHelper::HasTranslationFinished() {
    240   return ExecuteScriptAndGetBoolResult("cr.googleTranslate.finished", true);
    241 }
    242 
    243 bool TranslateHelper::HasTranslationFailed() {
    244   return ExecuteScriptAndGetBoolResult("cr.googleTranslate.error", true);
    245 }
    246 
    247 bool TranslateHelper::StartTranslation() {
    248   std::string script = "cr.googleTranslate.translate('" +
    249                        source_lang_ +
    250                        "','" +
    251                        target_lang_ +
    252                        "')";
    253   return ExecuteScriptAndGetBoolResult(script, false);
    254 }
    255 
    256 std::string TranslateHelper::GetOriginalPageLanguage() {
    257   return ExecuteScriptAndGetStringResult("cr.googleTranslate.sourceLang");
    258 }
    259 
    260 base::TimeDelta TranslateHelper::AdjustDelay(int delayInMs) {
    261   // Just converts |delayInMs| without any modification in practical cases.
    262   // Tests will override this function to return modified value.
    263   return base::TimeDelta::FromMilliseconds(delayInMs);
    264 }
    265 
    266 void TranslateHelper::ExecuteScript(const std::string& script) {
    267   WebFrame* main_frame = GetMainFrame();
    268   if (!main_frame)
    269     return;
    270 
    271   WebScriptSource source = WebScriptSource(ASCIIToUTF16(script));
    272   main_frame->executeScriptInIsolatedWorld(
    273       chrome::ISOLATED_WORLD_ID_TRANSLATE,
    274       &source,
    275       1,
    276       extensions::EXTENSION_GROUP_INTERNAL_TRANSLATE_SCRIPTS);
    277 }
    278 
    279 bool TranslateHelper::ExecuteScriptAndGetBoolResult(const std::string& script,
    280                                                     bool fallback) {
    281   WebFrame* main_frame = GetMainFrame();
    282   if (!main_frame)
    283     return fallback;
    284 
    285   v8::HandleScope handle_scope(v8::Isolate::GetCurrent());
    286   WebVector<v8::Local<v8::Value> > results;
    287   WebScriptSource source = WebScriptSource(ASCIIToUTF16(script));
    288   main_frame->executeScriptInIsolatedWorld(
    289       chrome::ISOLATED_WORLD_ID_TRANSLATE,
    290       &source,
    291       1,
    292       extensions::EXTENSION_GROUP_INTERNAL_TRANSLATE_SCRIPTS,
    293       &results);
    294   if (results.size() != 1 || results[0].IsEmpty() || !results[0]->IsBoolean()) {
    295     NOTREACHED();
    296     return fallback;
    297   }
    298 
    299   return results[0]->BooleanValue();
    300 }
    301 
    302 std::string TranslateHelper::ExecuteScriptAndGetStringResult(
    303     const std::string& script) {
    304   WebFrame* main_frame = GetMainFrame();
    305   if (!main_frame)
    306     return std::string();
    307 
    308   v8::HandleScope handle_scope(v8::Isolate::GetCurrent());
    309   WebVector<v8::Local<v8::Value> > results;
    310   WebScriptSource source = WebScriptSource(ASCIIToUTF16(script));
    311   main_frame->executeScriptInIsolatedWorld(
    312       chrome::ISOLATED_WORLD_ID_TRANSLATE,
    313       &source,
    314       1,
    315       extensions::EXTENSION_GROUP_INTERNAL_TRANSLATE_SCRIPTS,
    316       &results);
    317   if (results.size() != 1 || results[0].IsEmpty() || !results[0]->IsString()) {
    318     NOTREACHED();
    319     return std::string();
    320   }
    321 
    322   v8::Local<v8::String> v8_str = results[0]->ToString();
    323   int length = v8_str->Utf8Length() + 1;
    324   scoped_ptr<char[]> str(new char[length]);
    325   v8_str->WriteUtf8(str.get(), length);
    326   return std::string(str.get());
    327 }
    328 
    329 double TranslateHelper::ExecuteScriptAndGetDoubleResult(
    330     const std::string& script) {
    331   WebFrame* main_frame = GetMainFrame();
    332   if (!main_frame)
    333     return 0.0;
    334 
    335   v8::HandleScope handle_scope(v8::Isolate::GetCurrent());
    336   WebVector<v8::Local<v8::Value> > results;
    337   WebScriptSource source = WebScriptSource(ASCIIToUTF16(script));
    338   main_frame->executeScriptInIsolatedWorld(
    339       chrome::ISOLATED_WORLD_ID_TRANSLATE,
    340       &source,
    341       1,
    342       extensions::EXTENSION_GROUP_INTERNAL_TRANSLATE_SCRIPTS,
    343       &results);
    344   if (results.size() != 1 || results[0].IsEmpty() || !results[0]->IsNumber()) {
    345     NOTREACHED();
    346     return 0.0;
    347   }
    348 
    349   return results[0]->NumberValue();
    350 }
    351 
    352 ////////////////////////////////////////////////////////////////////////////////
    353 // TranslateHelper, private:
    354 //
    355 
    356 // static
    357 bool TranslateHelper::IsTranslationAllowed(WebDocument* document) {
    358   WebElement head = document->head();
    359   if (head.isNull() || !head.hasChildNodes())
    360     return true;
    361 
    362   const WebString meta(ASCIIToUTF16("meta"));
    363   const WebString name(ASCIIToUTF16("name"));
    364   const WebString google(ASCIIToUTF16("google"));
    365   const WebString value(ASCIIToUTF16("value"));
    366   const WebString content(ASCIIToUTF16("content"));
    367 
    368   WebNodeList children = head.childNodes();
    369   for (size_t i = 0; i < children.length(); ++i) {
    370     WebNode node = children.item(i);
    371     if (!node.isElementNode())
    372       continue;
    373     WebElement element = node.to<WebElement>();
    374     // Check if a tag is <meta>.
    375     if (!element.hasTagName(meta))
    376       continue;
    377     // Check if the tag contains name="google".
    378     WebString attribute = element.getAttribute(name);
    379     if (attribute.isNull() || attribute != google)
    380       continue;
    381     // Check if the tag contains value="notranslate", or content="notranslate".
    382     attribute = element.getAttribute(value);
    383     if (attribute.isNull())
    384       attribute = element.getAttribute(content);
    385     if (attribute.isNull())
    386       continue;
    387     if (LowerCaseEqualsASCII(attribute, "notranslate"))
    388       return false;
    389   }
    390   return true;
    391 }
    392 
    393 bool TranslateHelper::OnMessageReceived(const IPC::Message& message) {
    394   bool handled = true;
    395   IPC_BEGIN_MESSAGE_MAP(TranslateHelper, message)
    396     IPC_MESSAGE_HANDLER(ChromeViewMsg_TranslatePage, OnTranslatePage)
    397     IPC_MESSAGE_HANDLER(ChromeViewMsg_RevertTranslation, OnRevertTranslation)
    398 #if defined(CLD2_DYNAMIC_MODE)
    399     IPC_MESSAGE_HANDLER(ChromeViewMsg_CLDDataAvailable, OnCLDDataAvailable);
    400 #endif
    401     IPC_MESSAGE_UNHANDLED(handled = false)
    402   IPC_END_MESSAGE_MAP()
    403   return handled;
    404 }
    405 
    406 void TranslateHelper::OnTranslatePage(int page_id,
    407                                       const std::string& translate_script,
    408                                       const std::string& source_lang,
    409                                       const std::string& target_lang) {
    410   WebFrame* main_frame = GetMainFrame();
    411   if (!main_frame ||
    412       page_id_ != page_id ||
    413       render_view()->GetPageId() != page_id)
    414     return;  // We navigated away, nothing to do.
    415 
    416   // A similar translation is already under way, nothing to do.
    417   if (translation_pending_ && target_lang_ == target_lang)
    418     return;
    419 
    420   // Any pending translation is now irrelevant.
    421   CancelPendingTranslation();
    422 
    423   // Set our states.
    424   translation_pending_ = true;
    425 
    426   // If the source language is undetermined, we'll let the translate element
    427   // detect it.
    428   source_lang_ = (source_lang != translate::kUnknownLanguageCode) ?
    429                   source_lang : kAutoDetectionLanguage;
    430   target_lang_ = target_lang;
    431 
    432   translate::ReportUserActionDuration(language_determined_time_,
    433                                       base::TimeTicks::Now());
    434 
    435   GURL url(main_frame->document().url());
    436   translate::ReportPageScheme(url.scheme());
    437 
    438   // Set up v8 isolated world with proper content-security-policy and
    439   // security-origin.
    440   WebFrame* frame = GetMainFrame();
    441   if (frame) {
    442     frame->setIsolatedWorldContentSecurityPolicy(
    443         chrome::ISOLATED_WORLD_ID_TRANSLATE,
    444         WebString::fromUTF8(kContentSecurityPolicy));
    445 
    446     GURL security_origin = translate::GetTranslateSecurityOrigin();
    447     frame->setIsolatedWorldSecurityOrigin(
    448         chrome::ISOLATED_WORLD_ID_TRANSLATE,
    449         WebSecurityOrigin::create(security_origin));
    450   }
    451 
    452   if (!IsTranslateLibAvailable()) {
    453     // Evaluate the script to add the translation related method to the global
    454     // context of the page.
    455     ExecuteScript(translate_script);
    456     DCHECK(IsTranslateLibAvailable());
    457   }
    458 
    459   TranslatePageImpl(0);
    460 }
    461 
    462 void TranslateHelper::OnRevertTranslation(int page_id) {
    463   if (page_id_ != page_id || render_view()->GetPageId() != page_id)
    464     return;  // We navigated away, nothing to do.
    465 
    466   if (!IsTranslateLibAvailable()) {
    467     NOTREACHED();
    468     return;
    469   }
    470 
    471   CancelPendingTranslation();
    472 
    473   ExecuteScript("cr.googleTranslate.revert()");
    474 }
    475 
    476 void TranslateHelper::CheckTranslateStatus() {
    477   // If this is not the same page, the translation has been canceled.  If the
    478   // view is gone, the page is closing.
    479   if (page_id_ != render_view()->GetPageId() || !render_view()->GetWebView())
    480     return;
    481 
    482   // First check if there was an error.
    483   if (HasTranslationFailed()) {
    484     // TODO(toyoshim): Check |errorCode| of translate.js and notify it here.
    485     NotifyBrowserTranslationFailed(TranslateErrors::TRANSLATION_ERROR);
    486     return;  // There was an error.
    487   }
    488 
    489   if (HasTranslationFinished()) {
    490     std::string actual_source_lang;
    491     // Translation was successfull, if it was auto, retrieve the source
    492     // language the Translate Element detected.
    493     if (source_lang_ == kAutoDetectionLanguage) {
    494       actual_source_lang = GetOriginalPageLanguage();
    495       if (actual_source_lang.empty()) {
    496         NotifyBrowserTranslationFailed(TranslateErrors::UNKNOWN_LANGUAGE);
    497         return;
    498       } else if (actual_source_lang == target_lang_) {
    499         NotifyBrowserTranslationFailed(TranslateErrors::IDENTICAL_LANGUAGES);
    500         return;
    501       }
    502     } else {
    503       actual_source_lang = source_lang_;
    504     }
    505 
    506     if (!translation_pending_) {
    507       NOTREACHED();
    508       return;
    509     }
    510 
    511     translation_pending_ = false;
    512 
    513     // Check JavaScript performance counters for UMA reports.
    514     translate::ReportTimeToTranslate(
    515         ExecuteScriptAndGetDoubleResult("cr.googleTranslate.translationTime"));
    516 
    517     // Notify the browser we are done.
    518     render_view()->Send(new ChromeViewHostMsg_PageTranslated(
    519         render_view()->GetRoutingID(), render_view()->GetPageId(),
    520         actual_source_lang, target_lang_, TranslateErrors::NONE));
    521     return;
    522   }
    523 
    524   // The translation is still pending, check again later.
    525   base::MessageLoop::current()->PostDelayedTask(
    526       FROM_HERE,
    527       base::Bind(&TranslateHelper::CheckTranslateStatus,
    528                  weak_method_factory_.GetWeakPtr()),
    529       AdjustDelay(kTranslateStatusCheckDelayMs));
    530 }
    531 
    532 void TranslateHelper::TranslatePageImpl(int count) {
    533   DCHECK_LT(count, kMaxTranslateInitCheckAttempts);
    534   if (page_id_ != render_view()->GetPageId() || !render_view()->GetWebView())
    535     return;
    536 
    537   if (!IsTranslateLibReady()) {
    538     // The library is not ready, try again later, unless we have tried several
    539     // times unsucessfully already.
    540     if (++count >= kMaxTranslateInitCheckAttempts) {
    541       NotifyBrowserTranslationFailed(TranslateErrors::INITIALIZATION_ERROR);
    542       return;
    543     }
    544     base::MessageLoop::current()->PostDelayedTask(
    545         FROM_HERE,
    546         base::Bind(&TranslateHelper::TranslatePageImpl,
    547                    weak_method_factory_.GetWeakPtr(),
    548                    count),
    549         AdjustDelay(count * kTranslateInitCheckDelayMs));
    550     return;
    551   }
    552 
    553   // The library is loaded, and ready for translation now.
    554   // Check JavaScript performance counters for UMA reports.
    555   translate::ReportTimeToBeReady(
    556       ExecuteScriptAndGetDoubleResult("cr.googleTranslate.readyTime"));
    557   translate::ReportTimeToLoad(
    558       ExecuteScriptAndGetDoubleResult("cr.googleTranslate.loadTime"));
    559 
    560   if (!StartTranslation()) {
    561     NotifyBrowserTranslationFailed(TranslateErrors::TRANSLATION_ERROR);
    562     return;
    563   }
    564   // Check the status of the translation.
    565   base::MessageLoop::current()->PostDelayedTask(
    566       FROM_HERE,
    567       base::Bind(&TranslateHelper::CheckTranslateStatus,
    568                  weak_method_factory_.GetWeakPtr()),
    569       AdjustDelay(kTranslateStatusCheckDelayMs));
    570 }
    571 
    572 void TranslateHelper::NotifyBrowserTranslationFailed(
    573     TranslateErrors::Type error) {
    574   translation_pending_ = false;
    575   // Notify the browser there was an error.
    576   render_view()->Send(new ChromeViewHostMsg_PageTranslated(
    577       render_view()->GetRoutingID(), page_id_, source_lang_,
    578       target_lang_, error));
    579 }
    580 
    581 WebFrame* TranslateHelper::GetMainFrame() {
    582   WebView* web_view = render_view()->GetWebView();
    583 
    584   // When the tab is going to be closed, the web_view can be NULL.
    585   if (!web_view)
    586     return NULL;
    587 
    588   return web_view->mainFrame();
    589 }
    590 
    591 #if defined(CLD2_DYNAMIC_MODE)
    592 void TranslateHelper::CancelCLD2DataFilePolling() {
    593   cld2_data_file_polling_canceled_ = true;
    594 }
    595 
    596 void TranslateHelper::SendCLD2DataFileRequest(const int delay_millis,
    597                                               const int next_delay_millis) {
    598   // Terminate immediately if told to stop polling.
    599   if (cld2_data_file_polling_canceled_)
    600     return;
    601 
    602   // Terminate immediately if data is already loaded.
    603   if (CLD2::isDataLoaded())
    604     return;
    605 
    606   // Else, send the IPC message to the browser process requesting the data...
    607   Send(new ChromeViewHostMsg_NeedCLDData(routing_id()));
    608 
    609   // ... and enqueue another delayed task to call again. This will start a
    610   // chain of polling that will last until the pointer stops being NULL,
    611   // which is the right thing to do.
    612   // NB: In the great majority of cases, the data file will be available and
    613   // the very first delayed task will be a no-op that terminates the chain.
    614   // It's only while downloading the file that this will chain for a
    615   // nontrivial amount of time.
    616   // Use a weak pointer to avoid keeping this helper object around forever.
    617   base::MessageLoop::current()->PostDelayedTask(
    618       FROM_HERE,
    619       base::Bind(&TranslateHelper::SendCLD2DataFileRequest,
    620                  weak_method_factory_.GetWeakPtr(),
    621                  next_delay_millis, next_delay_millis),
    622       base::TimeDelta::FromMilliseconds(delay_millis));
    623 }
    624 
    625 void TranslateHelper::OnCLDDataAvailable(
    626     const IPC::PlatformFileForTransit ipc_file_handle,
    627     const uint64 data_offset,
    628     const uint64 data_length) {
    629   LoadCLDDData(IPC::PlatformFileForTransitToFile(ipc_file_handle), data_offset,
    630                data_length);
    631   if (deferred_page_capture_ && CLD2::isDataLoaded()) {
    632     deferred_page_capture_ = false; // Don't do this a second time.
    633     PageCaptured(deferred_page_id_, deferred_contents_);
    634     deferred_page_id_ = -1; // Clean up for sanity
    635     deferred_contents_.clear(); // Clean up for sanity
    636   }
    637 }
    638 
    639 void TranslateHelper::LoadCLDDData(
    640     base::File file,
    641     const uint64 data_offset,
    642     const uint64 data_length) {
    643   // Terminate immediately if told to stop polling.
    644   if (cld2_data_file_polling_canceled_)
    645     return;
    646 
    647   // Terminate immediately if data is already loaded.
    648   if (CLD2::isDataLoaded())
    649     return;
    650 
    651   if (!file.IsValid()) {
    652     LOG(ERROR) << "Can't find the CLD data file.";
    653     return;
    654   }
    655 
    656   // mmap the file
    657   s_cld_mmap_.Get().value = new base::MemoryMappedFile();
    658   bool initialized = s_cld_mmap_.Get().value->Initialize(file.Pass());
    659   if (!initialized) {
    660     LOG(ERROR) << "mmap initialization failed";
    661     delete s_cld_mmap_.Get().value;
    662     s_cld_mmap_.Get().value = NULL;
    663     return;
    664   }
    665 
    666   // Sanity checks
    667   uint64 max_int32 = std::numeric_limits<int32>::max();
    668   if (data_length + data_offset > s_cld_mmap_.Get().value->length()
    669       || data_length > max_int32) { // max signed 32 bit integer
    670     LOG(ERROR) << "Illegal mmap config: data_offset="
    671         << data_offset << ", data_length=" << data_length
    672         << ", mmap->length()=" << s_cld_mmap_.Get().value->length();
    673     delete s_cld_mmap_.Get().value;
    674     s_cld_mmap_.Get().value = NULL;
    675     return;
    676   }
    677 
    678   // Initialize the CLD subsystem... and it's all done!
    679   const uint8* data_ptr = s_cld_mmap_.Get().value->data() + data_offset;
    680   CLD2::loadDataFromRawAddress(data_ptr, data_length);
    681   DCHECK(CLD2::isDataLoaded()) << "Failed to load CLD data from mmap";
    682 }
    683 #endif
    684