Home | History | Annotate | Download | only in renderer
      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/chrome_render_view_observer.h"
      6 
      7 #include "base/bind.h"
      8 #include "base/bind_helpers.h"
      9 #include "base/command_line.h"
     10 #include "base/debug/trace_event.h"
     11 #include "base/message_loop/message_loop.h"
     12 #include "base/metrics/histogram.h"
     13 #include "base/strings/string_split.h"
     14 #include "base/strings/string_util.h"
     15 #include "base/strings/utf_string_conversions.h"
     16 #include "chrome/common/chrome_constants.h"
     17 #include "chrome/common/chrome_switches.h"
     18 #include "chrome/common/prerender_messages.h"
     19 #include "chrome/common/render_messages.h"
     20 #include "chrome/common/url_constants.h"
     21 #include "chrome/renderer/chrome_render_process_observer.h"
     22 #include "chrome/renderer/external_host_bindings.h"
     23 #include "chrome/renderer/prerender/prerender_helper.h"
     24 #include "chrome/renderer/safe_browsing/phishing_classifier_delegate.h"
     25 #include "chrome/renderer/translate/translate_helper.h"
     26 #include "chrome/renderer/webview_color_overlay.h"
     27 #include "content/public/common/bindings_policy.h"
     28 #include "content/public/renderer/content_renderer_client.h"
     29 #include "content/public/renderer/render_frame.h"
     30 #include "content/public/renderer/render_view.h"
     31 #include "extensions/common/constants.h"
     32 #include "extensions/common/stack_frame.h"
     33 #include "net/base/data_url.h"
     34 #include "skia/ext/image_operations.h"
     35 #include "skia/ext/platform_canvas.h"
     36 #include "third_party/WebKit/public/platform/WebCString.h"
     37 #include "third_party/WebKit/public/platform/WebRect.h"
     38 #include "third_party/WebKit/public/platform/WebSize.h"
     39 #include "third_party/WebKit/public/platform/WebString.h"
     40 #include "third_party/WebKit/public/platform/WebURLRequest.h"
     41 #include "third_party/WebKit/public/platform/WebVector.h"
     42 #include "third_party/WebKit/public/web/WebAXObject.h"
     43 #include "third_party/WebKit/public/web/WebDataSource.h"
     44 #include "third_party/WebKit/public/web/WebDocument.h"
     45 #include "third_party/WebKit/public/web/WebElement.h"
     46 #include "third_party/WebKit/public/web/WebFrame.h"
     47 #include "third_party/WebKit/public/web/WebInputEvent.h"
     48 #include "third_party/WebKit/public/web/WebNode.h"
     49 #include "third_party/WebKit/public/web/WebNodeList.h"
     50 #include "third_party/WebKit/public/web/WebView.h"
     51 #include "ui/base/ui_base_switches_util.h"
     52 #include "ui/gfx/favicon_size.h"
     53 #include "ui/gfx/size.h"
     54 #include "ui/gfx/size_f.h"
     55 #include "ui/gfx/skbitmap_operations.h"
     56 #include "v8/include/v8-testing.h"
     57 
     58 using blink::WebAXObject;
     59 using blink::WebCString;
     60 using blink::WebDataSource;
     61 using blink::WebDocument;
     62 using blink::WebElement;
     63 using blink::WebFrame;
     64 using blink::WebGestureEvent;
     65 using blink::WebIconURL;
     66 using blink::WebNode;
     67 using blink::WebNodeList;
     68 using blink::WebRect;
     69 using blink::WebSecurityOrigin;
     70 using blink::WebSize;
     71 using blink::WebString;
     72 using blink::WebTouchEvent;
     73 using blink::WebURL;
     74 using blink::WebURLRequest;
     75 using blink::WebView;
     76 using blink::WebVector;
     77 using blink::WebWindowFeatures;
     78 
     79 // Delay in milliseconds that we'll wait before capturing the page contents
     80 // and thumbnail.
     81 static const int kDelayForCaptureMs = 500;
     82 
     83 // Typically, we capture the page data once the page is loaded.
     84 // Sometimes, the page never finishes to load, preventing the page capture
     85 // To workaround this problem, we always perform a capture after the following
     86 // delay.
     87 static const int kDelayForForcedCaptureMs = 6000;
     88 
     89 // define to write the time necessary for thumbnail/DOM text retrieval,
     90 // respectively, into the system debug log
     91 // #define TIME_TEXT_RETRIEVAL
     92 
     93 // maximum number of characters in the document to index, any text beyond this
     94 // point will be clipped
     95 static const size_t kMaxIndexChars = 65535;
     96 
     97 // Constants for UMA statistic collection.
     98 static const char kTranslateCaptureText[] = "Translate.CaptureText";
     99 
    100 namespace {
    101 
    102 GURL StripRef(const GURL& url) {
    103   GURL::Replacements replacements;
    104   replacements.ClearRef();
    105   return url.ReplaceComponents(replacements);
    106 }
    107 
    108 // If the source image is null or occupies less area than
    109 // |thumbnail_min_area_pixels|, we return the image unmodified.  Otherwise, we
    110 // scale down the image so that the width and height do not exceed
    111 // |thumbnail_max_size_pixels|, preserving the original aspect ratio.
    112 SkBitmap Downscale(blink::WebImage image,
    113                    int thumbnail_min_area_pixels,
    114                    gfx::Size thumbnail_max_size_pixels) {
    115   if (image.isNull())
    116     return SkBitmap();
    117 
    118   gfx::Size image_size = image.size();
    119 
    120   if (image_size.GetArea() < thumbnail_min_area_pixels)
    121     return image.getSkBitmap();
    122 
    123   if (image_size.width() <= thumbnail_max_size_pixels.width() &&
    124       image_size.height() <= thumbnail_max_size_pixels.height())
    125     return image.getSkBitmap();
    126 
    127   gfx::SizeF scaled_size = image_size;
    128 
    129   if (scaled_size.width() > thumbnail_max_size_pixels.width()) {
    130     scaled_size.Scale(thumbnail_max_size_pixels.width() / scaled_size.width());
    131   }
    132 
    133   if (scaled_size.height() > thumbnail_max_size_pixels.height()) {
    134     scaled_size.Scale(
    135         thumbnail_max_size_pixels.height() / scaled_size.height());
    136   }
    137 
    138   return skia::ImageOperations::Resize(image.getSkBitmap(),
    139                                        skia::ImageOperations::RESIZE_GOOD,
    140                                        static_cast<int>(scaled_size.width()),
    141                                        static_cast<int>(scaled_size.height()));
    142 }
    143 
    144 // The delimiter for a stack trace provided by WebKit.
    145 const char kStackFrameDelimiter[] = "\n    at ";
    146 
    147 // Get a stack trace from a WebKit console message.
    148 // There are three possible scenarios:
    149 // 1. WebKit gives us a stack trace in |stack_trace|.
    150 // 2. The stack trace is embedded in the error |message| by an internal
    151 //    script. This will be more useful than |stack_trace|, since |stack_trace|
    152 //    will include the internal bindings trace, instead of a developer's code.
    153 // 3. No stack trace is included. In this case, we should mock one up from
    154 //    the given line number and source.
    155 // |message| will be populated with the error message only (i.e., will not
    156 // include any stack trace).
    157 extensions::StackTrace GetStackTraceFromMessage(
    158     base::string16* message,
    159     const base::string16& source,
    160     const base::string16& stack_trace,
    161     int32 line_number) {
    162   extensions::StackTrace result;
    163   std::vector<base::string16> pieces;
    164   size_t index = 0;
    165 
    166   if (message->find(base::UTF8ToUTF16(kStackFrameDelimiter)) !=
    167           base::string16::npos) {
    168     base::SplitStringUsingSubstr(*message,
    169                                  base::UTF8ToUTF16(kStackFrameDelimiter),
    170                                  &pieces);
    171     *message = pieces[0];
    172     index = 1;
    173   } else if (!stack_trace.empty()) {
    174     base::SplitStringUsingSubstr(stack_trace,
    175                                  base::UTF8ToUTF16(kStackFrameDelimiter),
    176                                  &pieces);
    177   }
    178 
    179   // If we got a stack trace, parse each frame from the text.
    180   if (index < pieces.size()) {
    181     for (; index < pieces.size(); ++index) {
    182       scoped_ptr<extensions::StackFrame> frame =
    183           extensions::StackFrame::CreateFromText(pieces[index]);
    184       if (frame.get())
    185         result.push_back(*frame);
    186     }
    187   }
    188 
    189   if (result.empty()) {  // If we don't have a stack trace, mock one up.
    190     result.push_back(
    191         extensions::StackFrame(line_number,
    192                                1u,  // column number
    193                                source,
    194                                base::string16() /* no function name */ ));
    195   }
    196 
    197   return result;
    198 }
    199 
    200 }  // namespace
    201 
    202 ChromeRenderViewObserver::ChromeRenderViewObserver(
    203     content::RenderView* render_view,
    204     ChromeRenderProcessObserver* chrome_render_process_observer)
    205     : content::RenderViewObserver(render_view),
    206       chrome_render_process_observer_(chrome_render_process_observer),
    207       translate_helper_(new TranslateHelper(render_view)),
    208       phishing_classifier_(NULL),
    209       last_indexed_page_id_(-1),
    210       capture_timer_(false, false) {
    211   const CommandLine& command_line = *CommandLine::ForCurrentProcess();
    212   if (!command_line.HasSwitch(switches::kDisableClientSidePhishingDetection))
    213     OnSetClientSidePhishingDetection(true);
    214 }
    215 
    216 ChromeRenderViewObserver::~ChromeRenderViewObserver() {
    217 }
    218 
    219 bool ChromeRenderViewObserver::OnMessageReceived(const IPC::Message& message) {
    220   bool handled = true;
    221   IPC_BEGIN_MESSAGE_MAP(ChromeRenderViewObserver, message)
    222     IPC_MESSAGE_HANDLER(ChromeViewMsg_WebUIJavaScript, OnWebUIJavaScript)
    223     IPC_MESSAGE_HANDLER(ChromeViewMsg_HandleMessageFromExternalHost,
    224                         OnHandleMessageFromExternalHost)
    225     IPC_MESSAGE_HANDLER(ChromeViewMsg_JavaScriptStressTestControl,
    226                         OnJavaScriptStressTestControl)
    227     IPC_MESSAGE_HANDLER(ChromeViewMsg_SetClientSidePhishingDetection,
    228                         OnSetClientSidePhishingDetection)
    229     IPC_MESSAGE_HANDLER(ChromeViewMsg_SetVisuallyDeemphasized,
    230                         OnSetVisuallyDeemphasized)
    231     IPC_MESSAGE_HANDLER(ChromeViewMsg_RequestThumbnailForContextNode,
    232                         OnRequestThumbnailForContextNode)
    233     IPC_MESSAGE_HANDLER(ChromeViewMsg_GetFPS, OnGetFPS)
    234 #if defined(OS_ANDROID)
    235     IPC_MESSAGE_HANDLER(ChromeViewMsg_UpdateTopControlsState,
    236                         OnUpdateTopControlsState)
    237     IPC_MESSAGE_HANDLER(ChromeViewMsg_RetrieveWebappInformation,
    238                         OnRetrieveWebappInformation)
    239 #endif
    240     IPC_MESSAGE_HANDLER(ChromeViewMsg_SetWindowFeatures, OnSetWindowFeatures)
    241     IPC_MESSAGE_UNHANDLED(handled = false)
    242   IPC_END_MESSAGE_MAP()
    243 
    244   return handled;
    245 }
    246 
    247 void ChromeRenderViewObserver::OnWebUIJavaScript(
    248     const base::string16& frame_xpath,
    249     const base::string16& jscript,
    250     int id,
    251     bool notify_result) {
    252   webui_javascript_.reset(new WebUIJavaScript());
    253   webui_javascript_->frame_xpath = frame_xpath;
    254   webui_javascript_->jscript = jscript;
    255   webui_javascript_->id = id;
    256   webui_javascript_->notify_result = notify_result;
    257 }
    258 
    259 void ChromeRenderViewObserver::OnHandleMessageFromExternalHost(
    260     const std::string& message,
    261     const std::string& origin,
    262     const std::string& target) {
    263   if (message.empty())
    264     return;
    265   GetExternalHostBindings()->ForwardMessageFromExternalHost(message, origin,
    266                                                             target);
    267 }
    268 
    269 void ChromeRenderViewObserver::OnJavaScriptStressTestControl(int cmd,
    270                                                              int param) {
    271   if (cmd == kJavaScriptStressTestSetStressRunType) {
    272     v8::Testing::SetStressRunType(static_cast<v8::Testing::StressType>(param));
    273   } else if (cmd == kJavaScriptStressTestPrepareStressRun) {
    274     v8::Testing::PrepareStressRun(param);
    275   }
    276 }
    277 
    278 #if defined(OS_ANDROID)
    279 void ChromeRenderViewObserver::OnUpdateTopControlsState(
    280     content::TopControlsState constraints,
    281     content::TopControlsState current,
    282     bool animate) {
    283   render_view()->UpdateTopControlsState(constraints, current, animate);
    284 }
    285 
    286 void ChromeRenderViewObserver::OnRetrieveWebappInformation(
    287     const GURL& expected_url) {
    288   WebFrame* main_frame = render_view()->GetWebView()->mainFrame();
    289   WebDocument document =
    290       main_frame ? main_frame->document() : WebDocument();
    291 
    292   WebElement head = document.isNull() ? WebElement() : document.head();
    293   GURL document_url = document.isNull() ? GURL() : GURL(document.url());
    294 
    295   // Make sure we're checking the right page.
    296   bool success = document_url == expected_url;
    297 
    298   bool is_mobile_webapp_capable = false;
    299   bool is_apple_mobile_webapp_capable = false;
    300 
    301   // Search the DOM for the webapp <meta> tags.
    302   if (!head.isNull()) {
    303     WebNodeList children = head.childNodes();
    304     for (unsigned i = 0; i < children.length(); ++i) {
    305       WebNode child = children.item(i);
    306       if (!child.isElementNode())
    307         continue;
    308       WebElement elem = child.to<WebElement>();
    309 
    310       if (elem.hasTagName("meta") && elem.hasAttribute("name")) {
    311         std::string name = elem.getAttribute("name").utf8();
    312         WebString content = elem.getAttribute("content");
    313         if (LowerCaseEqualsASCII(content, "yes")) {
    314           if (name == "mobile-web-app-capable") {
    315             is_mobile_webapp_capable = true;
    316           } else if (name == "apple-mobile-web-app-capable") {
    317             is_apple_mobile_webapp_capable = true;
    318           }
    319         }
    320       }
    321     }
    322   } else {
    323     success = false;
    324   }
    325 
    326   bool is_only_apple_mobile_webapp_capable =
    327       is_apple_mobile_webapp_capable && !is_mobile_webapp_capable;
    328   if (main_frame && is_only_apple_mobile_webapp_capable) {
    329     blink::WebConsoleMessage message(
    330         blink::WebConsoleMessage::LevelWarning,
    331         "<meta name=\"apple-mobile-web-app-capable\" content=\"yes\"> is "
    332         "deprecated. Please include <meta name=\"mobile-web-app-capable\" "
    333         "content=\"yes\"> - "
    334         "http://developers.google.com/chrome/mobile/docs/installtohomescreen");
    335     main_frame->addMessageToConsole(message);
    336   }
    337 
    338   Send(new ChromeViewHostMsg_DidRetrieveWebappInformation(
    339       routing_id(),
    340       success,
    341       is_mobile_webapp_capable,
    342       is_apple_mobile_webapp_capable,
    343       expected_url));
    344 }
    345 #endif
    346 
    347 void ChromeRenderViewObserver::OnSetWindowFeatures(
    348     const WebWindowFeatures& window_features) {
    349   render_view()->GetWebView()->setWindowFeatures(window_features);
    350 }
    351 
    352 void ChromeRenderViewObserver::Navigate(const GURL& url) {
    353   // Execute cache clear operations that were postponed until a navigation
    354   // event (including tab reload).
    355   if (chrome_render_process_observer_)
    356     chrome_render_process_observer_->ExecutePendingClearCache();
    357 }
    358 
    359 void ChromeRenderViewObserver::OnSetClientSidePhishingDetection(
    360     bool enable_phishing_detection) {
    361 #if defined(FULL_SAFE_BROWSING) && !defined(OS_CHROMEOS)
    362   phishing_classifier_ = enable_phishing_detection ?
    363       safe_browsing::PhishingClassifierDelegate::Create(
    364           render_view(), NULL) :
    365       NULL;
    366 #endif
    367 }
    368 
    369 void ChromeRenderViewObserver::OnSetVisuallyDeemphasized(bool deemphasized) {
    370   bool already_deemphasized = !!dimmed_color_overlay_.get();
    371   if (already_deemphasized == deemphasized)
    372     return;
    373 
    374   if (deemphasized) {
    375     // 70% opaque grey.
    376     SkColor greyish = SkColorSetARGB(178, 0, 0, 0);
    377     dimmed_color_overlay_.reset(
    378         new WebViewColorOverlay(render_view(), greyish));
    379   } else {
    380     dimmed_color_overlay_.reset();
    381   }
    382 }
    383 
    384 void ChromeRenderViewObserver::OnRequestThumbnailForContextNode(
    385     int thumbnail_min_area_pixels, gfx::Size thumbnail_max_size_pixels) {
    386   WebNode context_node = render_view()->GetContextMenuNode();
    387   SkBitmap thumbnail;
    388   gfx::Size original_size;
    389   if (!context_node.isNull() && context_node.isElementNode()) {
    390     blink::WebImage image = context_node.to<WebElement>().imageContents();
    391     original_size = image.size();
    392     thumbnail = Downscale(image,
    393                           thumbnail_min_area_pixels,
    394                           thumbnail_max_size_pixels);
    395   }
    396   Send(new ChromeViewHostMsg_RequestThumbnailForContextNode_ACK(
    397       routing_id(), thumbnail, original_size));
    398 }
    399 
    400 void ChromeRenderViewObserver::OnGetFPS() {
    401   float fps = (render_view()->GetFilteredTimePerFrame() > 0.0f)?
    402       1.0f / render_view()->GetFilteredTimePerFrame() : 0.0f;
    403   Send(new ChromeViewHostMsg_FPS(routing_id(), fps));
    404 }
    405 
    406 void ChromeRenderViewObserver::DidStartLoading() {
    407   if ((render_view()->GetEnabledBindings() & content::BINDINGS_POLICY_WEB_UI) &&
    408       webui_javascript_.get()) {
    409     render_view()->EvaluateScript(webui_javascript_->frame_xpath,
    410                                   webui_javascript_->jscript,
    411                                   webui_javascript_->id,
    412                                   webui_javascript_->notify_result);
    413     webui_javascript_.reset();
    414   }
    415 }
    416 
    417 void ChromeRenderViewObserver::DidStopLoading() {
    418   WebFrame* main_frame = render_view()->GetWebView()->mainFrame();
    419   GURL osd_url = main_frame->document().openSearchDescriptionURL();
    420   if (!osd_url.is_empty()) {
    421     Send(new ChromeViewHostMsg_PageHasOSDD(
    422         routing_id(), render_view()->GetPageId(), osd_url,
    423         search_provider::AUTODETECTED_PROVIDER));
    424   }
    425 
    426   // Don't capture pages including refresh meta tag.
    427   if (HasRefreshMetaTag(main_frame))
    428     return;
    429 
    430   CapturePageInfoLater(
    431       render_view()->GetPageId(),
    432       false,  // preliminary_capture
    433       base::TimeDelta::FromMilliseconds(
    434           render_view()->GetContentStateImmediately() ?
    435               0 : kDelayForCaptureMs));
    436 }
    437 
    438 void ChromeRenderViewObserver::DidCommitProvisionalLoad(
    439     WebFrame* frame, bool is_new_navigation) {
    440   // Don't capture pages being not new, or including refresh meta tag.
    441   if (!is_new_navigation || HasRefreshMetaTag(frame))
    442     return;
    443 
    444   CapturePageInfoLater(
    445       render_view()->GetPageId(),
    446       true,  // preliminary_capture
    447       base::TimeDelta::FromMilliseconds(kDelayForForcedCaptureMs));
    448 }
    449 
    450 void ChromeRenderViewObserver::DidClearWindowObject(WebFrame* frame) {
    451   if (render_view()->GetEnabledBindings() &
    452           content::BINDINGS_POLICY_EXTERNAL_HOST) {
    453     GetExternalHostBindings()->BindToJavascript(frame, "externalHost");
    454   }
    455 }
    456 
    457 void ChromeRenderViewObserver::DetailedConsoleMessageAdded(
    458     const base::string16& message,
    459     const base::string16& source,
    460     const base::string16& stack_trace_string,
    461     int32 line_number,
    462     int32 severity_level) {
    463   base::string16 trimmed_message = message;
    464   extensions::StackTrace stack_trace = GetStackTraceFromMessage(
    465       &trimmed_message,
    466       source,
    467       stack_trace_string,
    468       line_number);
    469   Send(new ChromeViewHostMsg_DetailedConsoleMessageAdded(routing_id(),
    470                                                          trimmed_message,
    471                                                          source,
    472                                                          stack_trace,
    473                                                          severity_level));
    474 }
    475 
    476 void ChromeRenderViewObserver::CapturePageInfoLater(int page_id,
    477                                                     bool preliminary_capture,
    478                                                     base::TimeDelta delay) {
    479   capture_timer_.Start(
    480       FROM_HERE,
    481       delay,
    482       base::Bind(&ChromeRenderViewObserver::CapturePageInfo,
    483                  base::Unretained(this),
    484                  page_id,
    485                  preliminary_capture));
    486 }
    487 
    488 void ChromeRenderViewObserver::CapturePageInfo(int page_id,
    489                                                bool preliminary_capture) {
    490   // If |page_id| is obsolete, we should stop indexing and capturing a page.
    491   if (render_view()->GetPageId() != page_id)
    492     return;
    493 
    494   if (!render_view()->GetWebView())
    495     return;
    496 
    497   WebFrame* main_frame = render_view()->GetWebView()->mainFrame();
    498   if (!main_frame)
    499     return;
    500 
    501   // Don't index/capture pages that are in view source mode.
    502   if (main_frame->isViewSourceModeEnabled())
    503     return;
    504 
    505   // Don't index/capture pages that failed to load.  This only checks the top
    506   // level frame so the thumbnail may contain a frame that failed to load.
    507   WebDataSource* ds = main_frame->dataSource();
    508   if (ds && ds->hasUnreachableURL())
    509     return;
    510 
    511   // Don't index/capture pages that are being prerendered.
    512   if (prerender::PrerenderHelper::IsPrerendering(
    513           render_view()->GetMainRenderFrame())) {
    514     return;
    515   }
    516 
    517   // Retrieve the frame's full text (up to kMaxIndexChars), and pass it to the
    518   // translate helper for language detection and possible translation.
    519   base::string16 contents;
    520   base::TimeTicks capture_begin_time = base::TimeTicks::Now();
    521   CaptureText(main_frame, &contents);
    522   UMA_HISTOGRAM_TIMES(kTranslateCaptureText,
    523                       base::TimeTicks::Now() - capture_begin_time);
    524   if (translate_helper_)
    525     translate_helper_->PageCaptured(page_id, contents);
    526 
    527   // TODO(shess): Is indexing "Full text search" indexing?  In that
    528   // case more of this can go.
    529   // Skip indexing if this is not a new load.  Note that the case where
    530   // page_id == last_indexed_page_id_ is more complicated, since we need to
    531   // reindex if the toplevel URL has changed (such as from a redirect), even
    532   // though this may not cause the page id to be incremented.
    533   if (page_id < last_indexed_page_id_)
    534     return;
    535 
    536   bool same_page_id = last_indexed_page_id_ == page_id;
    537   if (!preliminary_capture)
    538     last_indexed_page_id_ = page_id;
    539 
    540   // Get the URL for this page.
    541   GURL url(main_frame->document().url());
    542   if (url.is_empty()) {
    543     if (!preliminary_capture)
    544       last_indexed_url_ = GURL();
    545     return;
    546   }
    547 
    548   // If the page id is unchanged, check whether the URL (ignoring fragments)
    549   // has changed.  If so, we need to reindex.  Otherwise, assume this is a
    550   // reload, in-page navigation, or some other load type where we don't want to
    551   // reindex.  Note: subframe navigations after onload increment the page id,
    552   // so these will trigger a reindex.
    553   GURL stripped_url(StripRef(url));
    554   if (same_page_id && stripped_url == last_indexed_url_)
    555     return;
    556 
    557   if (!preliminary_capture)
    558     last_indexed_url_ = stripped_url;
    559 
    560   TRACE_EVENT0("renderer", "ChromeRenderViewObserver::CapturePageInfo");
    561 
    562 #if defined(FULL_SAFE_BROWSING)
    563   // Will swap out the string.
    564   if (phishing_classifier_)
    565     phishing_classifier_->PageCaptured(&contents, preliminary_capture);
    566 #endif
    567 }
    568 
    569 void ChromeRenderViewObserver::CaptureText(WebFrame* frame,
    570                                            base::string16* contents) {
    571   contents->clear();
    572   if (!frame)
    573     return;
    574 
    575 #ifdef TIME_TEXT_RETRIEVAL
    576   double begin = time_util::GetHighResolutionTimeNow();
    577 #endif
    578 
    579   // get the contents of the frame
    580   *contents = frame->contentAsText(kMaxIndexChars);
    581 
    582 #ifdef TIME_TEXT_RETRIEVAL
    583   double end = time_util::GetHighResolutionTimeNow();
    584   char buf[128];
    585   sprintf_s(buf, "%d chars retrieved for indexing in %gms\n",
    586             contents.size(), (end - begin)*1000);
    587   OutputDebugStringA(buf);
    588 #endif
    589 
    590   // When the contents are clipped to the maximum, we don't want to have a
    591   // partial word indexed at the end that might have been clipped. Therefore,
    592   // terminate the string at the last space to ensure no words are clipped.
    593   if (contents->size() == kMaxIndexChars) {
    594     size_t last_space_index = contents->find_last_of(base::kWhitespaceUTF16);
    595     if (last_space_index == base::string16::npos)
    596       return;  // don't index if we got a huge block of text with no spaces
    597     contents->resize(last_space_index);
    598   }
    599 }
    600 
    601 ExternalHostBindings* ChromeRenderViewObserver::GetExternalHostBindings() {
    602   if (!external_host_bindings_.get()) {
    603     external_host_bindings_.reset(new ExternalHostBindings(
    604         render_view(), routing_id()));
    605   }
    606   return external_host_bindings_.get();
    607 }
    608 
    609 bool ChromeRenderViewObserver::HasRefreshMetaTag(WebFrame* frame) {
    610   if (!frame)
    611     return false;
    612   WebElement head = frame->document().head();
    613   if (head.isNull() || !head.hasChildNodes())
    614     return false;
    615 
    616   const WebString tag_name(ASCIIToUTF16("meta"));
    617   const WebString attribute_name(ASCIIToUTF16("http-equiv"));
    618 
    619   WebNodeList children = head.childNodes();
    620   for (size_t i = 0; i < children.length(); ++i) {
    621     WebNode node = children.item(i);
    622     if (!node.isElementNode())
    623       continue;
    624     WebElement element = node.to<WebElement>();
    625     if (!element.hasTagName(tag_name))
    626       continue;
    627     WebString value = element.getAttribute(attribute_name);
    628     if (value.isNull() || !LowerCaseEqualsASCII(value, "refresh"))
    629       continue;
    630     return true;
    631   }
    632   return false;
    633 }
    634