Roll Clang 206824:209387
[chromium-blink-merge.git] / chrome / renderer / chrome_render_view_observer.cc
blobd292b3ed860523651df4c7c8840f13d1714af748
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/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;
65 using blink::WebNode;
66 using blink::WebNodeList;
67 using blink::WebRect;
68 using blink::WebSecurityOrigin;
69 using blink::WebSize;
70 using blink::WebString;
71 using blink::WebTouchEvent;
72 using blink::WebURL;
73 using blink::WebURLRequest;
74 using blink::WebView;
75 using blink::WebVector;
76 using blink::WebWindowFeatures;
78 // Delay in milliseconds that we'll wait before capturing the page contents
79 // and thumbnail.
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
85 // delay.
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";
99 namespace {
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,
124 int32 line_number) {
125 extensions::StackTrace result;
126 std::vector<base::string16> pieces;
127 size_t index = 0;
129 if (message->find(base::UTF8ToUTF16(kStackFrameDelimiter)) !=
130 base::string16::npos) {
131 base::SplitStringUsingSubstr(*message,
132 base::UTF8ToUTF16(kStackFrameDelimiter),
133 &pieces);
134 *message = pieces[0];
135 index = 1;
136 } else if (!stack_trace.empty()) {
137 base::SplitStringUsingSubstr(stack_trace,
138 base::UTF8ToUTF16(kStackFrameDelimiter),
139 &pieces);
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]);
147 if (frame.get())
148 result.push_back(*frame);
152 if (result.empty()) { // If we don't have a stack trace, mock one up.
153 result.push_back(
154 extensions::StackFrame(line_number,
155 1u, // column number
156 source,
157 base::string16() /* no function name */ ));
160 return result;
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,
171 bool* found_tag,
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.
179 *found_tag = false;
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())
186 continue;
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();
193 *found_tag = true;
194 break;
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 = "";
206 success = false;
209 return success;
211 #endif
213 } // namespace
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) {
233 bool handled = true;
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_SetName, OnSetName)
239 IPC_MESSAGE_HANDLER(ChromeViewMsg_SetVisuallyDeemphasized,
240 OnSetVisuallyDeemphasized)
241 IPC_MESSAGE_HANDLER(ChromeViewMsg_GetFPS, OnGetFPS)
242 #if defined(OS_ANDROID)
243 IPC_MESSAGE_HANDLER(ChromeViewMsg_UpdateTopControlsState,
244 OnUpdateTopControlsState)
245 IPC_MESSAGE_HANDLER(ChromeViewMsg_RetrieveWebappInformation,
246 OnRetrieveWebappInformation)
247 IPC_MESSAGE_HANDLER(ChromeViewMsg_RetrieveMetaTagContent,
248 OnRetrieveMetaTagContent)
249 #endif
250 IPC_MESSAGE_HANDLER(ChromeViewMsg_SetWindowFeatures, OnSetWindowFeatures)
251 IPC_MESSAGE_UNHANDLED(handled = false)
252 IPC_END_MESSAGE_MAP()
254 return handled;
257 void ChromeRenderViewObserver::OnWebUIJavaScript(
258 const base::string16& javascript) {
259 webui_javascript_ = javascript;
262 #if defined(OS_ANDROID)
263 void ChromeRenderViewObserver::OnUpdateTopControlsState(
264 content::TopControlsState constraints,
265 content::TopControlsState current,
266 bool animate) {
267 render_view()->UpdateTopControlsState(constraints, current, animate);
270 void ChromeRenderViewObserver::OnRetrieveWebappInformation(
271 const GURL& expected_url) {
272 WebFrame* main_frame = render_view()->GetWebView()->mainFrame();
273 bool found_tag;
274 std::string content_str;
276 // Search for the "mobile-web-app-capable" tag.
277 bool mobile_parse_success = RetrieveMetaTagContent(
278 main_frame,
279 expected_url,
280 "mobile-web-app-capable",
281 &found_tag,
282 &content_str);
283 bool is_mobile_webapp_capable = mobile_parse_success && found_tag &&
284 LowerCaseEqualsASCII(content_str, "yes");
286 // Search for the "apple-mobile-web-app-capable" tag.
287 bool apple_parse_success = RetrieveMetaTagContent(
288 main_frame,
289 expected_url,
290 "apple-mobile-web-app-capable",
291 &found_tag,
292 &content_str);
293 bool is_apple_mobile_webapp_capable = apple_parse_success && found_tag &&
294 LowerCaseEqualsASCII(content_str, "yes");
296 bool is_only_apple_mobile_webapp_capable =
297 is_apple_mobile_webapp_capable && !is_mobile_webapp_capable;
298 if (main_frame && is_only_apple_mobile_webapp_capable) {
299 blink::WebConsoleMessage message(
300 blink::WebConsoleMessage::LevelWarning,
301 "<meta name=\"apple-mobile-web-app-capable\" content=\"yes\"> is "
302 "deprecated. Please include <meta name=\"mobile-web-app-capable\" "
303 "content=\"yes\"> - "
304 "http://developers.google.com/chrome/mobile/docs/installtohomescreen");
305 main_frame->addMessageToConsole(message);
308 Send(new ChromeViewHostMsg_DidRetrieveWebappInformation(
309 routing_id(),
310 mobile_parse_success && apple_parse_success,
311 is_mobile_webapp_capable,
312 is_apple_mobile_webapp_capable,
313 expected_url));
316 void ChromeRenderViewObserver::OnRetrieveMetaTagContent(
317 const GURL& expected_url,
318 const std::string tag_name) {
319 bool found_tag;
320 std::string content_str;
321 bool parsed_successfully = RetrieveMetaTagContent(
322 render_view()->GetWebView()->mainFrame(),
323 expected_url,
324 tag_name,
325 &found_tag,
326 &content_str);
328 Send(new ChromeViewHostMsg_DidRetrieveMetaTagContent(
329 routing_id(),
330 parsed_successfully && found_tag,
331 tag_name,
332 content_str,
333 expected_url));
335 #endif
337 void ChromeRenderViewObserver::OnSetWindowFeatures(
338 const WebWindowFeatures& window_features) {
339 render_view()->GetWebView()->setWindowFeatures(window_features);
342 void ChromeRenderViewObserver::Navigate(const GURL& url) {
343 // Execute cache clear operations that were postponed until a navigation
344 // event (including tab reload).
345 if (chrome_render_process_observer_)
346 chrome_render_process_observer_->ExecutePendingClearCache();
347 // Let translate_helper do any preparatory work for loading a URL.
348 if (translate_helper_)
349 translate_helper_->PrepareForUrl(url);
352 void ChromeRenderViewObserver::OnSetClientSidePhishingDetection(
353 bool enable_phishing_detection) {
354 #if defined(FULL_SAFE_BROWSING) && !defined(OS_CHROMEOS)
355 phishing_classifier_ = enable_phishing_detection ?
356 safe_browsing::PhishingClassifierDelegate::Create(
357 render_view(), NULL) :
358 NULL;
359 #endif
362 void ChromeRenderViewObserver::OnSetName(const std::string& name) {
363 if (!render_view()->GetWebView())
364 return;
366 render_view()->GetWebView()->mainFrame()->setName(WebString::fromUTF8(name));
369 void ChromeRenderViewObserver::OnSetVisuallyDeemphasized(bool deemphasized) {
370 bool already_deemphasized = !!dimmed_color_overlay_.get();
371 if (already_deemphasized == deemphasized)
372 return;
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();
384 void ChromeRenderViewObserver::OnGetFPS() {
385 float fps = (render_view()->GetFilteredTimePerFrame() > 0.0f)?
386 1.0f / render_view()->GetFilteredTimePerFrame() : 0.0f;
387 Send(new ChromeViewHostMsg_FPS(routing_id(), fps));
390 void ChromeRenderViewObserver::DidStartLoading() {
391 if ((render_view()->GetEnabledBindings() & content::BINDINGS_POLICY_WEB_UI) &&
392 !webui_javascript_.empty()) {
393 render_view()->GetMainRenderFrame()->ExecuteJavaScript(webui_javascript_);
394 webui_javascript_.clear();
398 void ChromeRenderViewObserver::DidStopLoading() {
399 WebFrame* main_frame = render_view()->GetWebView()->mainFrame();
400 GURL osdd_url = main_frame->document().openSearchDescriptionURL();
401 if (!osdd_url.is_empty()) {
402 Send(new ChromeViewHostMsg_PageHasOSDD(
403 routing_id(), main_frame->document().url(), osdd_url,
404 search_provider::AUTODETECTED_PROVIDER));
407 // Don't capture pages including refresh meta tag.
408 if (HasRefreshMetaTag(main_frame))
409 return;
411 CapturePageInfoLater(
412 render_view()->GetPageId(),
413 false, // preliminary_capture
414 base::TimeDelta::FromMilliseconds(
415 render_view()->GetContentStateImmediately() ?
416 0 : kDelayForCaptureMs));
419 void ChromeRenderViewObserver::DidCommitProvisionalLoad(
420 WebLocalFrame* frame, bool is_new_navigation) {
421 // Don't capture pages being not new, or including refresh meta tag.
422 if (!is_new_navigation || HasRefreshMetaTag(frame))
423 return;
425 CapturePageInfoLater(
426 render_view()->GetPageId(),
427 true, // preliminary_capture
428 base::TimeDelta::FromMilliseconds(kDelayForForcedCaptureMs));
431 void ChromeRenderViewObserver::DetailedConsoleMessageAdded(
432 const base::string16& message,
433 const base::string16& source,
434 const base::string16& stack_trace_string,
435 int32 line_number,
436 int32 severity_level) {
437 base::string16 trimmed_message = message;
438 extensions::StackTrace stack_trace = GetStackTraceFromMessage(
439 &trimmed_message,
440 source,
441 stack_trace_string,
442 line_number);
443 Send(new ChromeViewHostMsg_DetailedConsoleMessageAdded(routing_id(),
444 trimmed_message,
445 source,
446 stack_trace,
447 severity_level));
450 void ChromeRenderViewObserver::CapturePageInfoLater(int page_id,
451 bool preliminary_capture,
452 base::TimeDelta delay) {
453 capture_timer_.Start(
454 FROM_HERE,
455 delay,
456 base::Bind(&ChromeRenderViewObserver::CapturePageInfo,
457 base::Unretained(this),
458 page_id,
459 preliminary_capture));
462 void ChromeRenderViewObserver::CapturePageInfo(int page_id,
463 bool preliminary_capture) {
464 // If |page_id| is obsolete, we should stop indexing and capturing a page.
465 if (render_view()->GetPageId() != page_id)
466 return;
468 if (!render_view()->GetWebView())
469 return;
471 WebFrame* main_frame = render_view()->GetWebView()->mainFrame();
472 if (!main_frame)
473 return;
475 // Don't index/capture pages that are in view source mode.
476 if (main_frame->isViewSourceModeEnabled())
477 return;
479 // Don't index/capture pages that failed to load. This only checks the top
480 // level frame so the thumbnail may contain a frame that failed to load.
481 WebDataSource* ds = main_frame->dataSource();
482 if (ds && ds->hasUnreachableURL())
483 return;
485 // Don't index/capture pages that are being prerendered.
486 if (prerender::PrerenderHelper::IsPrerendering(
487 render_view()->GetMainRenderFrame())) {
488 return;
491 // Retrieve the frame's full text (up to kMaxIndexChars), and pass it to the
492 // translate helper for language detection and possible translation.
493 base::string16 contents;
494 base::TimeTicks capture_begin_time = base::TimeTicks::Now();
495 CaptureText(main_frame, &contents);
496 UMA_HISTOGRAM_TIMES(kTranslateCaptureText,
497 base::TimeTicks::Now() - capture_begin_time);
498 if (translate_helper_)
499 translate_helper_->PageCaptured(page_id, contents);
501 // TODO(shess): Is indexing "Full text search" indexing? In that
502 // case more of this can go.
503 // Skip indexing if this is not a new load. Note that the case where
504 // page_id == last_indexed_page_id_ is more complicated, since we need to
505 // reindex if the toplevel URL has changed (such as from a redirect), even
506 // though this may not cause the page id to be incremented.
507 if (page_id < last_indexed_page_id_)
508 return;
510 bool same_page_id = last_indexed_page_id_ == page_id;
511 if (!preliminary_capture)
512 last_indexed_page_id_ = page_id;
514 // Get the URL for this page.
515 GURL url(main_frame->document().url());
516 if (url.is_empty()) {
517 if (!preliminary_capture)
518 last_indexed_url_ = GURL();
519 return;
522 // If the page id is unchanged, check whether the URL (ignoring fragments)
523 // has changed. If so, we need to reindex. Otherwise, assume this is a
524 // reload, in-page navigation, or some other load type where we don't want to
525 // reindex. Note: subframe navigations after onload increment the page id,
526 // so these will trigger a reindex.
527 GURL stripped_url(StripRef(url));
528 if (same_page_id && stripped_url == last_indexed_url_)
529 return;
531 if (!preliminary_capture)
532 last_indexed_url_ = stripped_url;
534 TRACE_EVENT0("renderer", "ChromeRenderViewObserver::CapturePageInfo");
536 #if defined(FULL_SAFE_BROWSING)
537 // Will swap out the string.
538 if (phishing_classifier_)
539 phishing_classifier_->PageCaptured(&contents, preliminary_capture);
540 #endif
543 void ChromeRenderViewObserver::CaptureText(WebFrame* frame,
544 base::string16* contents) {
545 contents->clear();
546 if (!frame)
547 return;
549 #ifdef TIME_TEXT_RETRIEVAL
550 double begin = time_util::GetHighResolutionTimeNow();
551 #endif
553 // get the contents of the frame
554 *contents = frame->contentAsText(kMaxIndexChars);
556 #ifdef TIME_TEXT_RETRIEVAL
557 double end = time_util::GetHighResolutionTimeNow();
558 char buf[128];
559 sprintf_s(buf, "%d chars retrieved for indexing in %gms\n",
560 contents.size(), (end - begin)*1000);
561 OutputDebugStringA(buf);
562 #endif
564 // When the contents are clipped to the maximum, we don't want to have a
565 // partial word indexed at the end that might have been clipped. Therefore,
566 // terminate the string at the last space to ensure no words are clipped.
567 if (contents->size() == kMaxIndexChars) {
568 size_t last_space_index = contents->find_last_of(base::kWhitespaceUTF16);
569 if (last_space_index == base::string16::npos)
570 return; // don't index if we got a huge block of text with no spaces
571 contents->resize(last_space_index);
575 bool ChromeRenderViewObserver::HasRefreshMetaTag(WebFrame* frame) {
576 if (!frame)
577 return false;
578 WebElement head = frame->document().head();
579 if (head.isNull() || !head.hasChildNodes())
580 return false;
582 const WebString tag_name(base::ASCIIToUTF16("meta"));
583 const WebString attribute_name(base::ASCIIToUTF16("http-equiv"));
585 WebNodeList children = head.childNodes();
586 for (size_t i = 0; i < children.length(); ++i) {
587 WebNode node = children.item(i);
588 if (!node.isElementNode())
589 continue;
590 WebElement element = node.to<WebElement>();
591 if (!element.hasTagName(tag_name))
592 continue;
593 WebString value = element.getAttribute(attribute_name);
594 if (value.isNull() || !LowerCaseEqualsASCII(value, "refresh"))
595 continue;
596 return true;
598 return false;