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