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/crash_logging.h"
11 #include "base/message_loop/message_loop.h"
12 #include "base/metrics/histogram.h"
13 #include "base/strings/string_number_conversions.h"
14 #include "base/strings/string_util.h"
15 #include "base/strings/utf_string_conversions.h"
16 #include "base/trace_event/trace_event.h"
17 #include "chrome/common/chrome_constants.h"
18 #include "chrome/common/chrome_isolated_world_ids.h"
19 #include "chrome/common/chrome_switches.h"
20 #include "chrome/common/crash_keys.h"
21 #include "chrome/common/render_messages.h"
22 #include "chrome/common/url_constants.h"
23 #include "chrome/renderer/prerender/prerender_helper.h"
24 #include "chrome/renderer/safe_browsing/phishing_classifier_delegate.h"
25 #include "chrome/renderer/web_apps.h"
26 #include "components/translate/content/renderer/translate_helper.h"
27 #include "components/web_cache/renderer/web_cache_render_process_observer.h"
28 #include "content/public/common/bindings_policy.h"
29 #include "content/public/renderer/content_renderer_client.h"
30 #include "content/public/renderer/render_frame.h"
31 #include "content/public/renderer/render_view.h"
32 #include "extensions/common/constants.h"
33 #include "net/base/data_url.h"
34 #include "skia/ext/platform_canvas.h"
35 #include "third_party/WebKit/public/platform/WebCString.h"
36 #include "third_party/WebKit/public/platform/WebRect.h"
37 #include "third_party/WebKit/public/platform/WebSize.h"
38 #include "third_party/WebKit/public/platform/WebString.h"
39 #include "third_party/WebKit/public/platform/WebURLRequest.h"
40 #include "third_party/WebKit/public/platform/WebVector.h"
41 #include "third_party/WebKit/public/web/WebAXObject.h"
42 #include "third_party/WebKit/public/web/WebDataSource.h"
43 #include "third_party/WebKit/public/web/WebDocument.h"
44 #include "third_party/WebKit/public/web/WebElement.h"
45 #include "third_party/WebKit/public/web/WebInputEvent.h"
46 #include "third_party/WebKit/public/web/WebLocalFrame.h"
47 #include "third_party/WebKit/public/web/WebNode.h"
48 #include "third_party/WebKit/public/web/WebNodeList.h"
49 #include "third_party/WebKit/public/web/WebView.h"
50 #include "ui/base/ui_base_switches_util.h"
51 #include "ui/gfx/favicon_size.h"
52 #include "ui/gfx/geometry/size.h"
53 #include "ui/gfx/geometry/size_f.h"
54 #include "ui/gfx/skbitmap_operations.h"
55 #include "v8/include/v8-testing.h"
57 #if defined(ENABLE_EXTENSIONS)
58 #include "chrome/common/extensions/chrome_extension_messages.h"
61 using blink::WebAXObject
;
62 using blink::WebCString
;
63 using blink::WebDataSource
;
64 using blink::WebDocument
;
65 using blink::WebElement
;
66 using blink::WebFrame
;
67 using blink::WebGestureEvent
;
68 using blink::WebIconURL
;
69 using blink::WebLocalFrame
;
71 using blink::WebNodeList
;
73 using blink::WebSecurityOrigin
;
75 using blink::WebString
;
76 using blink::WebTouchEvent
;
78 using blink::WebURLRequest
;
80 using blink::WebVector
;
81 using blink::WebWindowFeatures
;
82 using content::RenderFrame
;
84 // Delay in milliseconds that we'll wait before capturing the page contents.
85 static const int kDelayForCaptureMs
= 500;
87 // Typically, we capture the page data once the page is loaded.
88 // Sometimes, the page never finishes to load, preventing the page capture
89 // To workaround this problem, we always perform a capture after the following
91 static const int kDelayForForcedCaptureMs
= 6000;
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;
97 // Constants for UMA statistic collection.
98 static const char kTranslateCaptureText
[] = "Translate.CaptureText";
100 PageInfo::PageInfo(PageInfoReceiver
* context
)
101 : context_(context
), capture_timer_(false, false) {
105 void PageInfo::CapturePageInfoLater(CaptureType capture_type
,
106 RenderFrame
* render_frame
,
107 base::TimeDelta delay
) {
108 capture_timer_
.Start(
110 base::Bind(&PageInfo::CapturePageInfo
, base::Unretained(this),
111 render_frame
, capture_type
));
114 void PageInfo::CapturePageInfo(RenderFrame
* render_frame
,
115 CaptureType capture_type
) {
119 WebLocalFrame
* frame
= render_frame
->GetWebFrame();
123 // Don't index/capture pages that are in view source mode.
124 if (frame
->isViewSourceModeEnabled())
127 // Don't index/capture pages that failed to load. This only checks the top
128 // level frame so the thumbnail may contain a frame that failed to load.
129 // TODO(dglazkov): Verify that this is actually necessary.
130 WebDataSource
* ds
= frame
->dataSource();
131 if (ds
&& ds
->hasUnreachableURL())
134 // Don't index/capture pages that are being prerendered.
135 if (prerender::PrerenderHelper::IsPrerendering(render_frame
)) {
139 // Retrieve the frame's full text (up to kMaxIndexChars), and pass it to the
140 // translate helper for language detection and possible translation.
141 base::string16 contents
;
142 base::TimeTicks capture_begin_time
= base::TimeTicks::Now();
143 CaptureText(frame
, &contents
);
144 UMA_HISTOGRAM_TIMES(kTranslateCaptureText
,
145 base::TimeTicks::Now() - capture_begin_time
);
146 context_
->PageCaptured(&contents
, capture_type
);
149 void PageInfo::CaptureText(WebLocalFrame
* frame
, base::string16
* content
) {
154 // get the contents of the frame
155 *content
= frame
->contentAsText(kMaxIndexChars
);
157 // When the contents are clipped to the maximum, we don't want to have a
158 // partial word indexed at the end that might have been clipped. Therefore,
159 // terminate the string at the last space to ensure no words are clipped.
160 if (content
->size() == kMaxIndexChars
) {
161 size_t last_space_index
= content
->find_last_of(base::kWhitespaceUTF16
);
162 if (last_space_index
!= base::string16::npos
)
163 content
->resize(last_space_index
);
167 ChromeRenderViewObserver::ChromeRenderViewObserver(
168 content::RenderView
* render_view
,
169 web_cache::WebCacheRenderProcessObserver
* web_cache_render_process_observer
)
170 : content::RenderViewObserver(render_view
),
171 web_cache_render_process_observer_(web_cache_render_process_observer
),
173 new translate::TranslateHelper(render_view
,
174 chrome::ISOLATED_WORLD_ID_TRANSLATE
,
176 extensions::kExtensionScheme
)),
177 phishing_classifier_(NULL
),
178 webview_visually_deemphasized_(false),
180 const base::CommandLine
& command_line
=
181 *base::CommandLine::ForCurrentProcess();
182 if (!command_line
.HasSwitch(switches::kDisableClientSidePhishingDetection
))
183 OnSetClientSidePhishingDetection(true);
186 ChromeRenderViewObserver::~ChromeRenderViewObserver() {
189 bool ChromeRenderViewObserver::OnMessageReceived(const IPC::Message
& message
) {
191 IPC_BEGIN_MESSAGE_MAP(ChromeRenderViewObserver
, message
)
192 #if !defined(OS_ANDROID) && !defined(OS_IOS)
193 IPC_MESSAGE_HANDLER(ChromeViewMsg_WebUIJavaScript
, OnWebUIJavaScript
)
195 #if defined(ENABLE_EXTENSIONS)
196 IPC_MESSAGE_HANDLER(ChromeViewMsg_SetVisuallyDeemphasized
,
197 OnSetVisuallyDeemphasized
)
199 #if defined(OS_ANDROID)
200 IPC_MESSAGE_HANDLER(ChromeViewMsg_UpdateTopControlsState
,
201 OnUpdateTopControlsState
)
203 IPC_MESSAGE_HANDLER(ChromeViewMsg_GetWebApplicationInfo
,
204 OnGetWebApplicationInfo
)
205 IPC_MESSAGE_HANDLER(ChromeViewMsg_SetClientSidePhishingDetection
,
206 OnSetClientSidePhishingDetection
)
207 IPC_MESSAGE_HANDLER(ChromeViewMsg_SetWindowFeatures
, OnSetWindowFeatures
)
208 IPC_MESSAGE_UNHANDLED(handled
= false)
209 IPC_END_MESSAGE_MAP()
214 #if !defined(OS_ANDROID) && !defined(OS_IOS)
215 void ChromeRenderViewObserver::OnWebUIJavaScript(
216 const base::string16
& javascript
) {
217 webui_javascript_
.push_back(javascript
);
221 #if defined(OS_ANDROID)
222 void ChromeRenderViewObserver::OnUpdateTopControlsState(
223 content::TopControlsState constraints
,
224 content::TopControlsState current
,
226 render_view()->UpdateTopControlsState(constraints
, current
, animate
);
230 void ChromeRenderViewObserver::OnGetWebApplicationInfo() {
231 WebFrame
* main_frame
= render_view()->GetWebView()->mainFrame();
234 WebApplicationInfo web_app_info
;
235 web_apps::ParseWebAppFromWebDocument(main_frame
, &web_app_info
);
237 // The warning below is specific to mobile but it doesn't hurt to show it even
238 // if the Chromium build is running on a desktop. It will get more exposition.
239 if (web_app_info
.mobile_capable
==
240 WebApplicationInfo::MOBILE_CAPABLE_APPLE
) {
241 blink::WebConsoleMessage
message(
242 blink::WebConsoleMessage::LevelWarning
,
243 "<meta name=\"apple-mobile-web-app-capable\" content=\"yes\"> is "
244 "deprecated. Please include <meta name=\"mobile-web-app-capable\" "
245 "content=\"yes\"> - "
246 "http://developers.google.com/chrome/mobile/docs/installtohomescreen");
247 main_frame
->addMessageToConsole(message
);
250 // Prune out any data URLs in the set of icons. The browser process expects
251 // any icon with a data URL to have originated from a favicon. We don't want
252 // to decode arbitrary data URLs in the browser process. See
253 // http://b/issue?id=1162972
254 for (std::vector
<WebApplicationInfo::IconInfo
>::iterator it
=
255 web_app_info
.icons
.begin(); it
!= web_app_info
.icons
.end();) {
256 if (it
->url
.SchemeIs(url::kDataScheme
))
257 it
= web_app_info
.icons
.erase(it
);
262 // Truncate the strings we send to the browser process.
264 web_app_info
.title
.substr(0, chrome::kMaxMetaTagAttributeLength
);
265 web_app_info
.description
=
266 web_app_info
.description
.substr(0, chrome::kMaxMetaTagAttributeLength
);
268 Send(new ChromeViewHostMsg_DidGetWebApplicationInfo(
269 routing_id(), web_app_info
));
272 void ChromeRenderViewObserver::OnSetWindowFeatures(
273 const WebWindowFeatures
& window_features
) {
274 render_view()->GetWebView()->setWindowFeatures(window_features
);
277 void ChromeRenderViewObserver::Navigate(const GURL
& url
) {
278 // Execute cache clear operations that were postponed until a navigation
279 // event (including tab reload).
280 if (web_cache_render_process_observer_
)
281 web_cache_render_process_observer_
->ExecutePendingClearCache();
282 // Let translate_helper do any preparatory work for loading a URL.
283 if (translate_helper_
)
284 translate_helper_
->PrepareForUrl(url
);
287 void ChromeRenderViewObserver::OnSetClientSidePhishingDetection(
288 bool enable_phishing_detection
) {
289 #if defined(FULL_SAFE_BROWSING) && !defined(OS_CHROMEOS)
290 phishing_classifier_
= enable_phishing_detection
?
291 safe_browsing::PhishingClassifierDelegate::Create(render_view(), NULL
) :
296 #if defined(ENABLE_EXTENSIONS)
297 void ChromeRenderViewObserver::OnSetVisuallyDeemphasized(bool deemphasized
) {
298 if (webview_visually_deemphasized_
== deemphasized
)
301 webview_visually_deemphasized_
= deemphasized
;
305 SkColor greyish
= SkColorSetARGB(178, 0, 0, 0);
306 render_view()->GetWebView()->setPageOverlayColor(greyish
);
308 render_view()->GetWebView()->setPageOverlayColor(SK_ColorTRANSPARENT
);
313 void ChromeRenderViewObserver::DidStartLoading() {
314 if ((render_view()->GetEnabledBindings() & content::BINDINGS_POLICY_WEB_UI
) &&
315 !webui_javascript_
.empty()) {
316 for (size_t i
= 0; i
< webui_javascript_
.size(); ++i
) {
317 render_view()->GetMainRenderFrame()->ExecuteJavaScript(
318 webui_javascript_
[i
]);
320 webui_javascript_
.clear();
324 void ChromeRenderViewObserver::DidStopLoading() {
325 WebFrame
* main_frame
= render_view()->GetWebView()->mainFrame();
327 // Remote frames don't host a document, so return early if that's the case.
328 if (main_frame
->isWebRemoteFrame())
331 GURL osdd_url
= main_frame
->document().openSearchDescriptionURL();
332 if (!osdd_url
.is_empty()) {
333 Send(new ChromeViewHostMsg_PageHasOSDD(
334 routing_id(), main_frame
->document().url(), osdd_url
,
335 search_provider::AUTODETECTED_PROVIDER
));
338 // Don't capture pages including refresh meta tag.
339 if (HasRefreshMetaTag(main_frame
))
342 page_info_
.CapturePageInfoLater(
343 FINAL_CAPTURE
, render_view()->GetMainRenderFrame(),
344 base::TimeDelta::FromMilliseconds(
345 render_view()->GetContentStateImmediately() ? 0
346 : kDelayForCaptureMs
));
349 void ChromeRenderViewObserver::DidCommitProvisionalLoad(
350 WebLocalFrame
* frame
, bool is_new_navigation
) {
351 // Don't capture pages being not new, or including refresh meta tag.
352 if (!is_new_navigation
|| HasRefreshMetaTag(frame
))
355 base::debug::SetCrashKeyValue(
356 crash_keys::kViewCount
,
357 base::SizeTToString(content::RenderView::GetRenderViewCount()));
359 page_info_
.CapturePageInfoLater(
360 PRELIMINARY_CAPTURE
, render_view()->GetMainRenderFrame(),
361 base::TimeDelta::FromMilliseconds(kDelayForForcedCaptureMs
));
364 bool ChromeRenderViewObserver::HasRefreshMetaTag(WebFrame
* frame
) {
367 WebElement head
= frame
->document().head();
368 if (head
.isNull() || !head
.hasChildNodes())
371 const WebString
tag_name(base::ASCIIToUTF16("meta"));
372 const WebString
attribute_name(base::ASCIIToUTF16("http-equiv"));
374 WebNodeList children
= head
.childNodes();
375 for (size_t i
= 0; i
< children
.length(); ++i
) {
376 WebNode node
= children
.item(i
);
377 if (!node
.isElementNode())
379 WebElement element
= node
.to
<WebElement
>();
380 if (!element
.hasHTMLTagName(tag_name
))
382 WebString value
= element
.getAttribute(attribute_name
);
383 if (value
.isNull() ||
384 !base::LowerCaseEqualsASCII(base::StringPiece16(value
), "refresh"))
391 void ChromeRenderViewObserver::PageCaptured(base::string16
* content
,
392 CaptureType capture_type
) {
393 if (translate_helper_
)
394 translate_helper_
->PageCaptured(*content
);
396 TRACE_EVENT0("renderer", "ChromeRenderViewObserver::CapturePageInfo");
398 #if defined(FULL_SAFE_BROWSING)
399 // Will swap out the string.
400 if (phishing_classifier_
)
401 phishing_classifier_
->PageCaptured(content
,
402 capture_type
== PRELIMINARY_CAPTURE
);