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