• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
StripRef(const GURL & url)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.
Downscale(blink::WebImage image,int thumbnail_min_area_pixels,gfx::Size thumbnail_max_size_pixels)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).
GetStackTraceFromMessage(base::string16 * message,const base::string16 & source,const base::string16 & stack_trace,int32 line_number)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 
ChromeRenderViewObserver(content::RenderView * render_view,ChromeRenderProcessObserver * chrome_render_process_observer)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 
~ChromeRenderViewObserver()216 ChromeRenderViewObserver::~ChromeRenderViewObserver() {
217 }
218 
OnMessageReceived(const IPC::Message & message)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 
OnWebUIJavaScript(const base::string16 & frame_xpath,const base::string16 & jscript,int id,bool notify_result)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 
OnHandleMessageFromExternalHost(const std::string & message,const std::string & origin,const std::string & target)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 
OnJavaScriptStressTestControl(int cmd,int param)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)
OnUpdateTopControlsState(content::TopControlsState constraints,content::TopControlsState current,bool animate)279 void ChromeRenderViewObserver::OnUpdateTopControlsState(
280     content::TopControlsState constraints,
281     content::TopControlsState current,
282     bool animate) {
283   render_view()->UpdateTopControlsState(constraints, current, animate);
284 }
285 
OnRetrieveWebappInformation(const GURL & expected_url)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 
OnSetWindowFeatures(const WebWindowFeatures & window_features)347 void ChromeRenderViewObserver::OnSetWindowFeatures(
348     const WebWindowFeatures& window_features) {
349   render_view()->GetWebView()->setWindowFeatures(window_features);
350 }
351 
Navigate(const GURL & url)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 
OnSetClientSidePhishingDetection(bool enable_phishing_detection)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 
OnSetVisuallyDeemphasized(bool deemphasized)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 
OnRequestThumbnailForContextNode(int thumbnail_min_area_pixels,gfx::Size thumbnail_max_size_pixels)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 
OnGetFPS()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 
DidStartLoading()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 
DidStopLoading()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 
DidCommitProvisionalLoad(WebFrame * frame,bool is_new_navigation)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 
DidClearWindowObject(WebFrame * frame)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 
DetailedConsoleMessageAdded(const base::string16 & message,const base::string16 & source,const base::string16 & stack_trace_string,int32 line_number,int32 severity_level)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 
CapturePageInfoLater(int page_id,bool preliminary_capture,base::TimeDelta delay)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 
CapturePageInfo(int page_id,bool preliminary_capture)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 
CaptureText(WebFrame * frame,base::string16 * contents)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 
GetExternalHostBindings()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 
HasRefreshMetaTag(WebFrame * frame)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