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.
5 #include "chrome/renderer/chrome_render_view_observer.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/prerender/prerender_helper.h"
23 #include "chrome/renderer/safe_browsing/phishing_classifier_delegate.h"
24 #include "chrome/renderer/translate/translate_helper.h"
25 #include "chrome/renderer/webview_color_overlay.h"
26 #include "content/public/common/bindings_policy.h"
27 #include "content/public/renderer/content_renderer_client.h"
28 #include "content/public/renderer/render_frame.h"
29 #include "content/public/renderer/render_view.h"
30 #include "extensions/common/constants.h"
31 #include "extensions/common/stack_frame.h"
32 #include "net/base/data_url.h"
33 #include "skia/ext/platform_canvas.h"
34 #include "third_party/WebKit/public/platform/WebCString.h"
35 #include "third_party/WebKit/public/platform/WebRect.h"
36 #include "third_party/WebKit/public/platform/WebSize.h"
37 #include "third_party/WebKit/public/platform/WebString.h"
38 #include "third_party/WebKit/public/platform/WebURLRequest.h"
39 #include "third_party/WebKit/public/platform/WebVector.h"
40 #include "third_party/WebKit/public/web/WebAXObject.h"
41 #include "third_party/WebKit/public/web/WebDataSource.h"
42 #include "third_party/WebKit/public/web/WebDocument.h"
43 #include "third_party/WebKit/public/web/WebElement.h"
44 #include "third_party/WebKit/public/web/WebInputEvent.h"
45 #include "third_party/WebKit/public/web/WebLocalFrame.h"
46 #include "third_party/WebKit/public/web/WebNode.h"
47 #include "third_party/WebKit/public/web/WebNodeList.h"
48 #include "third_party/WebKit/public/web/WebView.h"
49 #include "ui/base/ui_base_switches_util.h"
50 #include "ui/gfx/favicon_size.h"
51 #include "ui/gfx/size.h"
52 #include "ui/gfx/size_f.h"
53 #include "ui/gfx/skbitmap_operations.h"
54 #include "v8/include/v8-testing.h"
56 using blink::WebAXObject
;
57 using blink::WebCString
;
58 using blink::WebDataSource
;
59 using blink::WebDocument
;
60 using blink::WebElement
;
61 using blink::WebFrame
;
62 using blink::WebGestureEvent
;
63 using blink::WebIconURL
;
64 using blink::WebLocalFrame
;
66 using blink::WebNodeList
;
68 using blink::WebSecurityOrigin
;
70 using blink::WebString
;
71 using blink::WebTouchEvent
;
73 using blink::WebURLRequest
;
75 using blink::WebVector
;
76 using blink::WebWindowFeatures
;
78 // Delay in milliseconds that we'll wait before capturing the page contents
80 static const int kDelayForCaptureMs
= 500;
82 // Typically, we capture the page data once the page is loaded.
83 // Sometimes, the page never finishes to load, preventing the page capture
84 // To workaround this problem, we always perform a capture after the following
86 static const int kDelayForForcedCaptureMs
= 6000;
88 // define to write the time necessary for thumbnail/DOM text retrieval,
89 // respectively, into the system debug log
90 // #define TIME_TEXT_RETRIEVAL
92 // maximum number of characters in the document to index, any text beyond this
93 // point will be clipped
94 static const size_t kMaxIndexChars
= 65535;
96 // Constants for UMA statistic collection.
97 static const char kTranslateCaptureText
[] = "Translate.CaptureText";
101 GURL
StripRef(const GURL
& url
) {
102 GURL::Replacements replacements
;
103 replacements
.ClearRef();
104 return url
.ReplaceComponents(replacements
);
107 // The delimiter for a stack trace provided by WebKit.
108 const char kStackFrameDelimiter
[] = "\n at ";
110 // Get a stack trace from a WebKit console message.
111 // There are three possible scenarios:
112 // 1. WebKit gives us a stack trace in |stack_trace|.
113 // 2. The stack trace is embedded in the error |message| by an internal
114 // script. This will be more useful than |stack_trace|, since |stack_trace|
115 // will include the internal bindings trace, instead of a developer's code.
116 // 3. No stack trace is included. In this case, we should mock one up from
117 // the given line number and source.
118 // |message| will be populated with the error message only (i.e., will not
119 // include any stack trace).
120 extensions::StackTrace
GetStackTraceFromMessage(
121 base::string16
* message
,
122 const base::string16
& source
,
123 const base::string16
& stack_trace
,
125 extensions::StackTrace result
;
126 std::vector
<base::string16
> pieces
;
129 if (message
->find(base::UTF8ToUTF16(kStackFrameDelimiter
)) !=
130 base::string16::npos
) {
131 base::SplitStringUsingSubstr(*message
,
132 base::UTF8ToUTF16(kStackFrameDelimiter
),
134 *message
= pieces
[0];
136 } else if (!stack_trace
.empty()) {
137 base::SplitStringUsingSubstr(stack_trace
,
138 base::UTF8ToUTF16(kStackFrameDelimiter
),
142 // If we got a stack trace, parse each frame from the text.
143 if (index
< pieces
.size()) {
144 for (; index
< pieces
.size(); ++index
) {
145 scoped_ptr
<extensions::StackFrame
> frame
=
146 extensions::StackFrame::CreateFromText(pieces
[index
]);
148 result
.push_back(*frame
);
152 if (result
.empty()) { // If we don't have a stack trace, mock one up.
154 extensions::StackFrame(line_number
,
157 base::string16() /* no function name */ ));
163 #if defined(OS_ANDROID)
164 // Parses the DOM for a <meta> tag with a particular name.
165 // |meta_tag_content| is set to the contents of the 'content' attribute.
166 // |found_tag| is set to true if the tag was successfully found.
167 // Returns true if the document was parsed without errors.
168 bool RetrieveMetaTagContent(const WebFrame
* main_frame
,
169 const GURL
& expected_url
,
170 const std::string
& meta_tag_name
,
172 std::string
* meta_tag_content
) {
173 WebDocument document
=
174 main_frame
? main_frame
->document() : WebDocument();
175 WebElement head
= document
.isNull() ? WebElement() : document
.head();
176 GURL document_url
= document
.isNull() ? GURL() : GURL(document
.url());
178 // Search the DOM for the <meta> tag with the given name.
180 *meta_tag_content
= "";
181 if (!head
.isNull()) {
182 WebNodeList children
= head
.childNodes();
183 for (unsigned i
= 0; i
< children
.length(); ++i
) {
184 WebNode child
= children
.item(i
);
185 if (!child
.isElementNode())
187 WebElement elem
= child
.to
<WebElement
>();
188 if (elem
.hasTagName("meta")) {
189 if (elem
.hasAttribute("name") && elem
.hasAttribute("content")) {
190 std::string name
= elem
.getAttribute("name").utf8();
191 if (name
== meta_tag_name
) {
192 *meta_tag_content
= elem
.getAttribute("content").utf8();
201 // Make sure we're checking the right page and that the length of the content
202 // string is reasonable.
203 bool success
= document_url
== expected_url
;
204 if (meta_tag_content
->size() > chrome::kMaxMetaTagAttributeLength
) {
205 *meta_tag_content
= "";
215 ChromeRenderViewObserver::ChromeRenderViewObserver(
216 content::RenderView
* render_view
,
217 ChromeRenderProcessObserver
* chrome_render_process_observer
)
218 : content::RenderViewObserver(render_view
),
219 chrome_render_process_observer_(chrome_render_process_observer
),
220 translate_helper_(new TranslateHelper(render_view
)),
221 phishing_classifier_(NULL
),
222 last_indexed_page_id_(-1),
223 capture_timer_(false, false) {
224 const CommandLine
& command_line
= *CommandLine::ForCurrentProcess();
225 if (!command_line
.HasSwitch(switches::kDisableClientSidePhishingDetection
))
226 OnSetClientSidePhishingDetection(true);
229 ChromeRenderViewObserver::~ChromeRenderViewObserver() {
232 bool ChromeRenderViewObserver::OnMessageReceived(const IPC::Message
& message
) {
234 IPC_BEGIN_MESSAGE_MAP(ChromeRenderViewObserver
, message
)
235 IPC_MESSAGE_HANDLER(ChromeViewMsg_WebUIJavaScript
, OnWebUIJavaScript
)
236 IPC_MESSAGE_HANDLER(ChromeViewMsg_SetClientSidePhishingDetection
,
237 OnSetClientSidePhishingDetection
)
238 IPC_MESSAGE_HANDLER(ChromeViewMsg_SetVisuallyDeemphasized
,
239 OnSetVisuallyDeemphasized
)
240 IPC_MESSAGE_HANDLER(ChromeViewMsg_GetFPS
, OnGetFPS
)
241 #if defined(OS_ANDROID)
242 IPC_MESSAGE_HANDLER(ChromeViewMsg_UpdateTopControlsState
,
243 OnUpdateTopControlsState
)
244 IPC_MESSAGE_HANDLER(ChromeViewMsg_RetrieveWebappInformation
,
245 OnRetrieveWebappInformation
)
246 IPC_MESSAGE_HANDLER(ChromeViewMsg_RetrieveMetaTagContent
,
247 OnRetrieveMetaTagContent
)
249 IPC_MESSAGE_HANDLER(ChromeViewMsg_SetWindowFeatures
, OnSetWindowFeatures
)
250 IPC_MESSAGE_UNHANDLED(handled
= false)
251 IPC_END_MESSAGE_MAP()
256 void ChromeRenderViewObserver::OnWebUIJavaScript(
257 const base::string16
& javascript
) {
258 webui_javascript_
= javascript
;
261 #if defined(OS_ANDROID)
262 void ChromeRenderViewObserver::OnUpdateTopControlsState(
263 content::TopControlsState constraints
,
264 content::TopControlsState current
,
266 render_view()->UpdateTopControlsState(constraints
, current
, animate
);
269 void ChromeRenderViewObserver::OnRetrieveWebappInformation(
270 const GURL
& expected_url
) {
271 WebFrame
* main_frame
= render_view()->GetWebView()->mainFrame();
273 std::string content_str
;
275 // Search for the "mobile-web-app-capable" tag.
276 bool mobile_parse_success
= RetrieveMetaTagContent(
279 "mobile-web-app-capable",
282 bool is_mobile_webapp_capable
= mobile_parse_success
&& found_tag
&&
283 LowerCaseEqualsASCII(content_str
, "yes");
285 // Search for the "apple-mobile-web-app-capable" tag.
286 bool apple_parse_success
= RetrieveMetaTagContent(
289 "apple-mobile-web-app-capable",
292 bool is_apple_mobile_webapp_capable
= apple_parse_success
&& found_tag
&&
293 LowerCaseEqualsASCII(content_str
, "yes");
295 bool is_only_apple_mobile_webapp_capable
=
296 is_apple_mobile_webapp_capable
&& !is_mobile_webapp_capable
;
297 if (main_frame
&& is_only_apple_mobile_webapp_capable
) {
298 blink::WebConsoleMessage
message(
299 blink::WebConsoleMessage::LevelWarning
,
300 "<meta name=\"apple-mobile-web-app-capable\" content=\"yes\"> is "
301 "deprecated. Please include <meta name=\"mobile-web-app-capable\" "
302 "content=\"yes\"> - "
303 "http://developers.google.com/chrome/mobile/docs/installtohomescreen");
304 main_frame
->addMessageToConsole(message
);
307 Send(new ChromeViewHostMsg_DidRetrieveWebappInformation(
309 mobile_parse_success
&& apple_parse_success
,
310 is_mobile_webapp_capable
,
311 is_apple_mobile_webapp_capable
,
315 void ChromeRenderViewObserver::OnRetrieveMetaTagContent(
316 const GURL
& expected_url
,
317 const std::string tag_name
) {
319 std::string content_str
;
320 bool parsed_successfully
= RetrieveMetaTagContent(
321 render_view()->GetWebView()->mainFrame(),
327 Send(new ChromeViewHostMsg_DidRetrieveMetaTagContent(
329 parsed_successfully
&& found_tag
,
336 void ChromeRenderViewObserver::OnSetWindowFeatures(
337 const WebWindowFeatures
& window_features
) {
338 render_view()->GetWebView()->setWindowFeatures(window_features
);
341 void ChromeRenderViewObserver::Navigate(const GURL
& url
) {
342 // Execute cache clear operations that were postponed until a navigation
343 // event (including tab reload).
344 if (chrome_render_process_observer_
)
345 chrome_render_process_observer_
->ExecutePendingClearCache();
346 // Let translate_helper do any preparatory work for loading a URL.
347 if (translate_helper_
)
348 translate_helper_
->PrepareForUrl(url
);
351 void ChromeRenderViewObserver::OnSetClientSidePhishingDetection(
352 bool enable_phishing_detection
) {
353 #if defined(FULL_SAFE_BROWSING) && !defined(OS_CHROMEOS)
354 phishing_classifier_
= enable_phishing_detection
?
355 safe_browsing::PhishingClassifierDelegate::Create(
356 render_view(), NULL
) :
361 void ChromeRenderViewObserver::OnSetVisuallyDeemphasized(bool deemphasized
) {
362 bool already_deemphasized
= !!dimmed_color_overlay_
.get();
363 if (already_deemphasized
== deemphasized
)
368 SkColor greyish
= SkColorSetARGB(178, 0, 0, 0);
369 dimmed_color_overlay_
.reset(
370 new WebViewColorOverlay(render_view(), greyish
));
372 dimmed_color_overlay_
.reset();
376 void ChromeRenderViewObserver::OnGetFPS() {
377 float fps
= (render_view()->GetFilteredTimePerFrame() > 0.0f
)?
378 1.0f
/ render_view()->GetFilteredTimePerFrame() : 0.0f
;
379 Send(new ChromeViewHostMsg_FPS(routing_id(), fps
));
382 void ChromeRenderViewObserver::DidStartLoading() {
383 if ((render_view()->GetEnabledBindings() & content::BINDINGS_POLICY_WEB_UI
) &&
384 !webui_javascript_
.empty()) {
385 render_view()->GetMainRenderFrame()->ExecuteJavaScript(webui_javascript_
);
386 webui_javascript_
.clear();
390 void ChromeRenderViewObserver::DidStopLoading() {
391 WebFrame
* main_frame
= render_view()->GetWebView()->mainFrame();
392 GURL osd_url
= main_frame
->document().openSearchDescriptionURL();
393 if (!osd_url
.is_empty()) {
394 Send(new ChromeViewHostMsg_PageHasOSDD(
395 routing_id(), render_view()->GetPageId(), osd_url
,
396 search_provider::AUTODETECTED_PROVIDER
));
399 // Don't capture pages including refresh meta tag.
400 if (HasRefreshMetaTag(main_frame
))
403 CapturePageInfoLater(
404 render_view()->GetPageId(),
405 false, // preliminary_capture
406 base::TimeDelta::FromMilliseconds(
407 render_view()->GetContentStateImmediately() ?
408 0 : kDelayForCaptureMs
));
411 void ChromeRenderViewObserver::DidCommitProvisionalLoad(
412 WebLocalFrame
* frame
, bool is_new_navigation
) {
413 // Don't capture pages being not new, or including refresh meta tag.
414 if (!is_new_navigation
|| HasRefreshMetaTag(frame
))
417 CapturePageInfoLater(
418 render_view()->GetPageId(),
419 true, // preliminary_capture
420 base::TimeDelta::FromMilliseconds(kDelayForForcedCaptureMs
));
423 void ChromeRenderViewObserver::DetailedConsoleMessageAdded(
424 const base::string16
& message
,
425 const base::string16
& source
,
426 const base::string16
& stack_trace_string
,
428 int32 severity_level
) {
429 base::string16 trimmed_message
= message
;
430 extensions::StackTrace stack_trace
= GetStackTraceFromMessage(
435 Send(new ChromeViewHostMsg_DetailedConsoleMessageAdded(routing_id(),
442 void ChromeRenderViewObserver::CapturePageInfoLater(int page_id
,
443 bool preliminary_capture
,
444 base::TimeDelta delay
) {
445 capture_timer_
.Start(
448 base::Bind(&ChromeRenderViewObserver::CapturePageInfo
,
449 base::Unretained(this),
451 preliminary_capture
));
454 void ChromeRenderViewObserver::CapturePageInfo(int page_id
,
455 bool preliminary_capture
) {
456 // If |page_id| is obsolete, we should stop indexing and capturing a page.
457 if (render_view()->GetPageId() != page_id
)
460 if (!render_view()->GetWebView())
463 WebFrame
* main_frame
= render_view()->GetWebView()->mainFrame();
467 // Don't index/capture pages that are in view source mode.
468 if (main_frame
->isViewSourceModeEnabled())
471 // Don't index/capture pages that failed to load. This only checks the top
472 // level frame so the thumbnail may contain a frame that failed to load.
473 WebDataSource
* ds
= main_frame
->dataSource();
474 if (ds
&& ds
->hasUnreachableURL())
477 // Don't index/capture pages that are being prerendered.
478 if (prerender::PrerenderHelper::IsPrerendering(
479 render_view()->GetMainRenderFrame())) {
483 // Retrieve the frame's full text (up to kMaxIndexChars), and pass it to the
484 // translate helper for language detection and possible translation.
485 base::string16 contents
;
486 base::TimeTicks capture_begin_time
= base::TimeTicks::Now();
487 CaptureText(main_frame
, &contents
);
488 UMA_HISTOGRAM_TIMES(kTranslateCaptureText
,
489 base::TimeTicks::Now() - capture_begin_time
);
490 if (translate_helper_
)
491 translate_helper_
->PageCaptured(page_id
, contents
);
493 // TODO(shess): Is indexing "Full text search" indexing? In that
494 // case more of this can go.
495 // Skip indexing if this is not a new load. Note that the case where
496 // page_id == last_indexed_page_id_ is more complicated, since we need to
497 // reindex if the toplevel URL has changed (such as from a redirect), even
498 // though this may not cause the page id to be incremented.
499 if (page_id
< last_indexed_page_id_
)
502 bool same_page_id
= last_indexed_page_id_
== page_id
;
503 if (!preliminary_capture
)
504 last_indexed_page_id_
= page_id
;
506 // Get the URL for this page.
507 GURL
url(main_frame
->document().url());
508 if (url
.is_empty()) {
509 if (!preliminary_capture
)
510 last_indexed_url_
= GURL();
514 // If the page id is unchanged, check whether the URL (ignoring fragments)
515 // has changed. If so, we need to reindex. Otherwise, assume this is a
516 // reload, in-page navigation, or some other load type where we don't want to
517 // reindex. Note: subframe navigations after onload increment the page id,
518 // so these will trigger a reindex.
519 GURL
stripped_url(StripRef(url
));
520 if (same_page_id
&& stripped_url
== last_indexed_url_
)
523 if (!preliminary_capture
)
524 last_indexed_url_
= stripped_url
;
526 TRACE_EVENT0("renderer", "ChromeRenderViewObserver::CapturePageInfo");
528 #if defined(FULL_SAFE_BROWSING)
529 // Will swap out the string.
530 if (phishing_classifier_
)
531 phishing_classifier_
->PageCaptured(&contents
, preliminary_capture
);
535 void ChromeRenderViewObserver::CaptureText(WebFrame
* frame
,
536 base::string16
* contents
) {
541 #ifdef TIME_TEXT_RETRIEVAL
542 double begin
= time_util::GetHighResolutionTimeNow();
545 // get the contents of the frame
546 *contents
= frame
->contentAsText(kMaxIndexChars
);
548 #ifdef TIME_TEXT_RETRIEVAL
549 double end
= time_util::GetHighResolutionTimeNow();
551 sprintf_s(buf
, "%d chars retrieved for indexing in %gms\n",
552 contents
.size(), (end
- begin
)*1000);
553 OutputDebugStringA(buf
);
556 // When the contents are clipped to the maximum, we don't want to have a
557 // partial word indexed at the end that might have been clipped. Therefore,
558 // terminate the string at the last space to ensure no words are clipped.
559 if (contents
->size() == kMaxIndexChars
) {
560 size_t last_space_index
= contents
->find_last_of(base::kWhitespaceUTF16
);
561 if (last_space_index
== base::string16::npos
)
562 return; // don't index if we got a huge block of text with no spaces
563 contents
->resize(last_space_index
);
567 bool ChromeRenderViewObserver::HasRefreshMetaTag(WebFrame
* frame
) {
570 WebElement head
= frame
->document().head();
571 if (head
.isNull() || !head
.hasChildNodes())
574 const WebString
tag_name(base::ASCIIToUTF16("meta"));
575 const WebString
attribute_name(base::ASCIIToUTF16("http-equiv"));
577 WebNodeList children
= head
.childNodes();
578 for (size_t i
= 0; i
< children
.length(); ++i
) {
579 WebNode node
= children
.item(i
);
580 if (!node
.isElementNode())
582 WebElement element
= node
.to
<WebElement
>();
583 if (!element
.hasTagName(tag_name
))
585 WebString value
= element
.getAttribute(attribute_name
);
586 if (value
.isNull() || !LowerCaseEqualsASCII(value
, "refresh"))