Make sure GN deps rolls include the optional GN bots.
[chromium-blink-merge.git] / chrome / renderer / chrome_render_view_observer.cc
blobe125e6bf20a3a7d7aab5dc139d0282c6c833a88a
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"
7 #include "base/bind.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/prerender_messages.h"
22 #include "chrome/common/render_messages.h"
23 #include "chrome/common/url_constants.h"
24 #include "chrome/renderer/prerender/prerender_helper.h"
25 #include "chrome/renderer/safe_browsing/phishing_classifier_delegate.h"
26 #include "chrome/renderer/web_apps.h"
27 #include "chrome/renderer/webview_color_overlay.h"
28 #include "components/translate/content/renderer/translate_helper.h"
29 #include "components/web_cache/renderer/web_cache_render_process_observer.h"
30 #include "content/public/common/bindings_policy.h"
31 #include "content/public/renderer/content_renderer_client.h"
32 #include "content/public/renderer/render_frame.h"
33 #include "content/public/renderer/render_view.h"
34 #include "extensions/common/constants.h"
35 #include "net/base/data_url.h"
36 #include "skia/ext/platform_canvas.h"
37 #include "third_party/WebKit/public/platform/WebCString.h"
38 #include "third_party/WebKit/public/platform/WebRect.h"
39 #include "third_party/WebKit/public/platform/WebSize.h"
40 #include "third_party/WebKit/public/platform/WebString.h"
41 #include "third_party/WebKit/public/platform/WebURLRequest.h"
42 #include "third_party/WebKit/public/platform/WebVector.h"
43 #include "third_party/WebKit/public/web/WebAXObject.h"
44 #include "third_party/WebKit/public/web/WebDataSource.h"
45 #include "third_party/WebKit/public/web/WebDocument.h"
46 #include "third_party/WebKit/public/web/WebElement.h"
47 #include "third_party/WebKit/public/web/WebInputEvent.h"
48 #include "third_party/WebKit/public/web/WebLocalFrame.h"
49 #include "third_party/WebKit/public/web/WebNode.h"
50 #include "third_party/WebKit/public/web/WebNodeList.h"
51 #include "third_party/WebKit/public/web/WebView.h"
52 #include "ui/base/ui_base_switches_util.h"
53 #include "ui/gfx/favicon_size.h"
54 #include "ui/gfx/geometry/size.h"
55 #include "ui/gfx/geometry/size_f.h"
56 #include "ui/gfx/skbitmap_operations.h"
57 #include "v8/include/v8-testing.h"
59 #if defined(ENABLE_EXTENSIONS)
60 #include "chrome/common/extensions/chrome_extension_messages.h"
61 #endif
63 using blink::WebAXObject;
64 using blink::WebCString;
65 using blink::WebDataSource;
66 using blink::WebDocument;
67 using blink::WebElement;
68 using blink::WebFrame;
69 using blink::WebGestureEvent;
70 using blink::WebIconURL;
71 using blink::WebLocalFrame;
72 using blink::WebNode;
73 using blink::WebNodeList;
74 using blink::WebRect;
75 using blink::WebSecurityOrigin;
76 using blink::WebSize;
77 using blink::WebString;
78 using blink::WebTouchEvent;
79 using blink::WebURL;
80 using blink::WebURLRequest;
81 using blink::WebView;
82 using blink::WebVector;
83 using blink::WebWindowFeatures;
85 // Delay in milliseconds that we'll wait before capturing the page contents
86 // and thumbnail.
87 static const int kDelayForCaptureMs = 500;
89 // Typically, we capture the page data once the page is loaded.
90 // Sometimes, the page never finishes to load, preventing the page capture
91 // To workaround this problem, we always perform a capture after the following
92 // delay.
93 static const int kDelayForForcedCaptureMs = 6000;
95 // define to write the time necessary for thumbnail/DOM text retrieval,
96 // respectively, into the system debug log
97 // #define TIME_TEXT_RETRIEVAL
99 // maximum number of characters in the document to index, any text beyond this
100 // point will be clipped
101 static const size_t kMaxIndexChars = 65535;
103 // Constants for UMA statistic collection.
104 static const char kTranslateCaptureText[] = "Translate.CaptureText";
106 namespace {
108 #if defined(OS_ANDROID)
109 // Parses the DOM for a <meta> tag with a particular name.
110 // |meta_tag_content| is set to the contents of the 'content' attribute.
111 // |found_tag| is set to true if the tag was successfully found.
112 // Returns true if the document was parsed without errors.
113 bool RetrieveMetaTagContent(const WebFrame* main_frame,
114 const GURL& expected_url,
115 const std::string& meta_tag_name,
116 bool* found_tag,
117 std::string* meta_tag_content) {
118 WebDocument document =
119 main_frame ? main_frame->document() : WebDocument();
120 WebElement head = document.isNull() ? WebElement() : document.head();
121 GURL document_url = document.isNull() ? GURL() : GURL(document.url());
123 // Search the DOM for the <meta> tag with the given name.
124 *found_tag = false;
125 *meta_tag_content = "";
126 if (!head.isNull()) {
127 WebNodeList children = head.childNodes();
128 for (unsigned i = 0; i < children.length(); ++i) {
129 WebNode child = children.item(i);
130 if (!child.isElementNode())
131 continue;
132 WebElement elem = child.to<WebElement>();
133 if (elem.hasHTMLTagName("meta")) {
134 if (elem.hasAttribute("name") && elem.hasAttribute("content")) {
135 std::string name = elem.getAttribute("name").utf8();
136 if (name == meta_tag_name) {
137 *meta_tag_content = elem.getAttribute("content").utf8();
138 *found_tag = true;
139 break;
146 // Make sure we're checking the right page and that the length of the content
147 // string is reasonable.
148 bool success = document_url == expected_url;
149 if (meta_tag_content->size() > chrome::kMaxMetaTagAttributeLength) {
150 *meta_tag_content = "";
151 success = false;
154 return success;
156 #endif
158 } // namespace
160 ChromeRenderViewObserver::ChromeRenderViewObserver(
161 content::RenderView* render_view,
162 web_cache::WebCacheRenderProcessObserver* web_cache_render_process_observer)
163 : content::RenderViewObserver(render_view),
164 web_cache_render_process_observer_(web_cache_render_process_observer),
165 translate_helper_(new translate::TranslateHelper(
166 render_view,
167 chrome::ISOLATED_WORLD_ID_TRANSLATE,
169 extensions::kExtensionScheme)),
170 phishing_classifier_(NULL),
171 capture_timer_(false, false) {
172 const base::CommandLine& command_line =
173 *base::CommandLine::ForCurrentProcess();
174 if (!command_line.HasSwitch(switches::kDisableClientSidePhishingDetection))
175 OnSetClientSidePhishingDetection(true);
178 ChromeRenderViewObserver::~ChromeRenderViewObserver() {
181 bool ChromeRenderViewObserver::OnMessageReceived(const IPC::Message& message) {
182 bool handled = true;
183 IPC_BEGIN_MESSAGE_MAP(ChromeRenderViewObserver, message)
184 #if !defined(OS_ANDROID) && !defined(OS_IOS)
185 IPC_MESSAGE_HANDLER(ChromeViewMsg_WebUIJavaScript, OnWebUIJavaScript)
186 #endif
187 #if defined(ENABLE_EXTENSIONS)
188 IPC_MESSAGE_HANDLER(ChromeViewMsg_SetVisuallyDeemphasized,
189 OnSetVisuallyDeemphasized)
190 #endif
191 #if defined(OS_ANDROID)
192 IPC_MESSAGE_HANDLER(ChromeViewMsg_UpdateTopControlsState,
193 OnUpdateTopControlsState)
194 IPC_MESSAGE_HANDLER(ChromeViewMsg_RetrieveMetaTagContent,
195 OnRetrieveMetaTagContent)
196 #endif
197 IPC_MESSAGE_HANDLER(ChromeViewMsg_GetWebApplicationInfo,
198 OnGetWebApplicationInfo)
199 IPC_MESSAGE_HANDLER(ChromeViewMsg_SetClientSidePhishingDetection,
200 OnSetClientSidePhishingDetection)
201 IPC_MESSAGE_HANDLER(ChromeViewMsg_SetWindowFeatures, OnSetWindowFeatures)
202 IPC_MESSAGE_UNHANDLED(handled = false)
203 IPC_END_MESSAGE_MAP()
205 return handled;
208 #if !defined(OS_ANDROID) && !defined(OS_IOS)
209 void ChromeRenderViewObserver::OnWebUIJavaScript(
210 const base::string16& javascript) {
211 webui_javascript_.push_back(javascript);
213 #endif
215 #if defined(OS_ANDROID)
216 void ChromeRenderViewObserver::OnUpdateTopControlsState(
217 content::TopControlsState constraints,
218 content::TopControlsState current,
219 bool animate) {
220 render_view()->UpdateTopControlsState(constraints, current, animate);
223 void ChromeRenderViewObserver::OnRetrieveMetaTagContent(
224 const GURL& expected_url,
225 const std::string tag_name) {
226 bool found_tag;
227 std::string content_str;
228 bool parsed_successfully = RetrieveMetaTagContent(
229 render_view()->GetWebView()->mainFrame(),
230 expected_url,
231 tag_name,
232 &found_tag,
233 &content_str);
235 Send(new ChromeViewHostMsg_DidRetrieveMetaTagContent(
236 routing_id(),
237 parsed_successfully && found_tag,
238 tag_name,
239 content_str,
240 expected_url));
242 #endif
244 void ChromeRenderViewObserver::OnGetWebApplicationInfo() {
245 WebFrame* main_frame = render_view()->GetWebView()->mainFrame();
246 DCHECK(main_frame);
248 WebApplicationInfo web_app_info;
249 web_apps::ParseWebAppFromWebDocument(main_frame, &web_app_info);
251 // The warning below is specific to mobile but it doesn't hurt to show it even
252 // if the Chromium build is running on a desktop. It will get more exposition.
253 if (web_app_info.mobile_capable ==
254 WebApplicationInfo::MOBILE_CAPABLE_APPLE) {
255 blink::WebConsoleMessage message(
256 blink::WebConsoleMessage::LevelWarning,
257 "<meta name=\"apple-mobile-web-app-capable\" content=\"yes\"> is "
258 "deprecated. Please include <meta name=\"mobile-web-app-capable\" "
259 "content=\"yes\"> - "
260 "http://developers.google.com/chrome/mobile/docs/installtohomescreen");
261 main_frame->addMessageToConsole(message);
264 // Prune out any data URLs in the set of icons. The browser process expects
265 // any icon with a data URL to have originated from a favicon. We don't want
266 // to decode arbitrary data URLs in the browser process. See
267 // http://b/issue?id=1162972
268 for (std::vector<WebApplicationInfo::IconInfo>::iterator it =
269 web_app_info.icons.begin(); it != web_app_info.icons.end();) {
270 if (it->url.SchemeIs(url::kDataScheme))
271 it = web_app_info.icons.erase(it);
272 else
273 ++it;
276 // Truncate the strings we send to the browser process.
277 web_app_info.title =
278 web_app_info.title.substr(0, chrome::kMaxMetaTagAttributeLength);
279 web_app_info.description =
280 web_app_info.description.substr(0, chrome::kMaxMetaTagAttributeLength);
282 Send(new ChromeViewHostMsg_DidGetWebApplicationInfo(
283 routing_id(), web_app_info));
286 void ChromeRenderViewObserver::OnSetWindowFeatures(
287 const WebWindowFeatures& window_features) {
288 render_view()->GetWebView()->setWindowFeatures(window_features);
291 void ChromeRenderViewObserver::Navigate(const GURL& url) {
292 // Execute cache clear operations that were postponed until a navigation
293 // event (including tab reload).
294 if (web_cache_render_process_observer_)
295 web_cache_render_process_observer_->ExecutePendingClearCache();
296 // Let translate_helper do any preparatory work for loading a URL.
297 if (translate_helper_)
298 translate_helper_->PrepareForUrl(url);
301 void ChromeRenderViewObserver::OnSetClientSidePhishingDetection(
302 bool enable_phishing_detection) {
303 #if defined(FULL_SAFE_BROWSING) && !defined(OS_CHROMEOS)
304 phishing_classifier_ = enable_phishing_detection ?
305 safe_browsing::PhishingClassifierDelegate::Create(render_view(), NULL) :
306 NULL;
307 #endif
310 #if defined(ENABLE_EXTENSIONS)
311 void ChromeRenderViewObserver::OnSetVisuallyDeemphasized(bool deemphasized) {
312 bool already_deemphasized = !!dimmed_color_overlay_.get();
313 if (already_deemphasized == deemphasized)
314 return;
316 if (deemphasized) {
317 // 70% opaque grey.
318 SkColor greyish = SkColorSetARGB(178, 0, 0, 0);
319 dimmed_color_overlay_.reset(
320 new WebViewColorOverlay(render_view(), greyish));
321 } else {
322 dimmed_color_overlay_.reset();
325 #endif
327 void ChromeRenderViewObserver::DidStartLoading() {
328 if ((render_view()->GetEnabledBindings() & content::BINDINGS_POLICY_WEB_UI) &&
329 !webui_javascript_.empty()) {
330 for (size_t i = 0; i < webui_javascript_.size(); ++i) {
331 render_view()->GetMainRenderFrame()->ExecuteJavaScript(
332 webui_javascript_[i]);
334 webui_javascript_.clear();
338 void ChromeRenderViewObserver::DidStopLoading() {
339 WebFrame* main_frame = render_view()->GetWebView()->mainFrame();
341 // Remote frames don't host a document, so return early if that's the case.
342 if (main_frame->isWebRemoteFrame())
343 return;
345 GURL osdd_url = main_frame->document().openSearchDescriptionURL();
346 if (!osdd_url.is_empty()) {
347 Send(new ChromeViewHostMsg_PageHasOSDD(
348 routing_id(), main_frame->document().url(), osdd_url,
349 search_provider::AUTODETECTED_PROVIDER));
352 // Don't capture pages including refresh meta tag.
353 if (HasRefreshMetaTag(main_frame))
354 return;
356 CapturePageInfoLater(
357 false, // preliminary_capture
358 base::TimeDelta::FromMilliseconds(
359 render_view()->GetContentStateImmediately() ?
360 0 : kDelayForCaptureMs));
363 void ChromeRenderViewObserver::DidCommitProvisionalLoad(
364 WebLocalFrame* frame, bool is_new_navigation) {
365 // Don't capture pages being not new, or including refresh meta tag.
366 if (!is_new_navigation || HasRefreshMetaTag(frame))
367 return;
369 base::debug::SetCrashKeyValue(
370 crash_keys::kViewCount,
371 base::SizeTToString(content::RenderView::GetRenderViewCount()));
373 CapturePageInfoLater(
374 true, // preliminary_capture
375 base::TimeDelta::FromMilliseconds(kDelayForForcedCaptureMs));
378 void ChromeRenderViewObserver::CapturePageInfoLater(bool preliminary_capture,
379 base::TimeDelta delay) {
380 capture_timer_.Start(
381 FROM_HERE,
382 delay,
383 base::Bind(&ChromeRenderViewObserver::CapturePageInfo,
384 base::Unretained(this),
385 preliminary_capture));
388 void ChromeRenderViewObserver::CapturePageInfo(bool preliminary_capture) {
389 if (!render_view()->GetWebView())
390 return;
392 WebFrame* main_frame = render_view()->GetWebView()->mainFrame();
393 if (!main_frame)
394 return;
396 // TODO(creis): Refactor WebFrame::contentAsText to handle RemoteFrames,
397 // likely by moving it to the browser process. For now, only capture page
398 // info from main frames that are LocalFrames, and ignore their RemoteFrame
399 // children.
400 if (main_frame->isWebRemoteFrame())
401 return;
403 // Don't index/capture pages that are in view source mode.
404 if (main_frame->isViewSourceModeEnabled())
405 return;
407 // Don't index/capture pages that failed to load. This only checks the top
408 // level frame so the thumbnail may contain a frame that failed to load.
409 WebDataSource* ds = main_frame->dataSource();
410 if (ds && ds->hasUnreachableURL())
411 return;
413 // Don't index/capture pages that are being prerendered.
414 if (prerender::PrerenderHelper::IsPrerendering(
415 render_view()->GetMainRenderFrame())) {
416 return;
419 // Retrieve the frame's full text (up to kMaxIndexChars), and pass it to the
420 // translate helper for language detection and possible translation.
421 base::string16 contents;
422 base::TimeTicks capture_begin_time = base::TimeTicks::Now();
423 CaptureText(main_frame, &contents);
424 UMA_HISTOGRAM_TIMES(kTranslateCaptureText,
425 base::TimeTicks::Now() - capture_begin_time);
426 if (translate_helper_)
427 translate_helper_->PageCaptured(contents);
429 TRACE_EVENT0("renderer", "ChromeRenderViewObserver::CapturePageInfo");
431 #if defined(FULL_SAFE_BROWSING)
432 // Will swap out the string.
433 if (phishing_classifier_)
434 phishing_classifier_->PageCaptured(&contents, preliminary_capture);
435 #endif
438 void ChromeRenderViewObserver::CaptureText(WebFrame* frame,
439 base::string16* contents) {
440 contents->clear();
441 if (!frame)
442 return;
444 #ifdef TIME_TEXT_RETRIEVAL
445 double begin = time_util::GetHighResolutionTimeNow();
446 #endif
448 // get the contents of the frame
449 *contents = frame->contentAsText(kMaxIndexChars);
451 #ifdef TIME_TEXT_RETRIEVAL
452 double end = time_util::GetHighResolutionTimeNow();
453 char buf[128];
454 sprintf_s(buf, "%d chars retrieved for indexing in %gms\n",
455 contents.size(), (end - begin)*1000);
456 OutputDebugStringA(buf);
457 #endif
459 // When the contents are clipped to the maximum, we don't want to have a
460 // partial word indexed at the end that might have been clipped. Therefore,
461 // terminate the string at the last space to ensure no words are clipped.
462 if (contents->size() == kMaxIndexChars) {
463 size_t last_space_index = contents->find_last_of(base::kWhitespaceUTF16);
464 if (last_space_index != base::string16::npos)
465 contents->resize(last_space_index);
469 bool ChromeRenderViewObserver::HasRefreshMetaTag(WebFrame* frame) {
470 if (!frame)
471 return false;
472 WebElement head = frame->document().head();
473 if (head.isNull() || !head.hasChildNodes())
474 return false;
476 const WebString tag_name(base::ASCIIToUTF16("meta"));
477 const WebString attribute_name(base::ASCIIToUTF16("http-equiv"));
479 WebNodeList children = head.childNodes();
480 for (size_t i = 0; i < children.length(); ++i) {
481 WebNode node = children.item(i);
482 if (!node.isElementNode())
483 continue;
484 WebElement element = node.to<WebElement>();
485 if (!element.hasHTMLTagName(tag_name))
486 continue;
487 WebString value = element.getAttribute(attribute_name);
488 if (value.isNull() || !base::LowerCaseEqualsASCII(value, "refresh"))
489 continue;
490 return true;
492 return false;