Added documentation to web_view.js/web_view_experimental.js regarding the webview...
[chromium-blink-merge.git] / chrome / renderer / chrome_render_view_observer.cc
blob47509babe540d3cf3a701cc254d80d9b999037a3
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/image_operations.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/WebFrame.h"
46 #include "third_party/WebKit/public/web/WebInputEvent.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/size.h"
53 #include "ui/gfx/size_f.h"
54 #include "ui/gfx/skbitmap_operations.h"
55 #include "v8/include/v8-testing.h"
57 using blink::WebAXObject;
58 using blink::WebCString;
59 using blink::WebDataSource;
60 using blink::WebDocument;
61 using blink::WebElement;
62 using blink::WebFrame;
63 using blink::WebGestureEvent;
64 using blink::WebIconURL;
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 // If the source image is null or occupies less area than
108 // |thumbnail_min_area_pixels|, we return the image unmodified. Otherwise, we
109 // scale down the image so that the width and height do not exceed
110 // |thumbnail_max_size_pixels|, preserving the original aspect ratio.
111 SkBitmap Downscale(blink::WebImage image,
112 int thumbnail_min_area_pixels,
113 gfx::Size thumbnail_max_size_pixels) {
114 if (image.isNull())
115 return SkBitmap();
117 gfx::Size image_size = image.size();
119 if (image_size.GetArea() < thumbnail_min_area_pixels)
120 return image.getSkBitmap();
122 if (image_size.width() <= thumbnail_max_size_pixels.width() &&
123 image_size.height() <= thumbnail_max_size_pixels.height())
124 return image.getSkBitmap();
126 gfx::SizeF scaled_size = image_size;
128 if (scaled_size.width() > thumbnail_max_size_pixels.width()) {
129 scaled_size.Scale(thumbnail_max_size_pixels.width() / scaled_size.width());
132 if (scaled_size.height() > thumbnail_max_size_pixels.height()) {
133 scaled_size.Scale(
134 thumbnail_max_size_pixels.height() / scaled_size.height());
137 return skia::ImageOperations::Resize(image.getSkBitmap(),
138 skia::ImageOperations::RESIZE_GOOD,
139 static_cast<int>(scaled_size.width()),
140 static_cast<int>(scaled_size.height()));
143 // The delimiter for a stack trace provided by WebKit.
144 const char kStackFrameDelimiter[] = "\n at ";
146 // Get a stack trace from a WebKit console message.
147 // There are three possible scenarios:
148 // 1. WebKit gives us a stack trace in |stack_trace|.
149 // 2. The stack trace is embedded in the error |message| by an internal
150 // script. This will be more useful than |stack_trace|, since |stack_trace|
151 // will include the internal bindings trace, instead of a developer's code.
152 // 3. No stack trace is included. In this case, we should mock one up from
153 // the given line number and source.
154 // |message| will be populated with the error message only (i.e., will not
155 // include any stack trace).
156 extensions::StackTrace GetStackTraceFromMessage(
157 base::string16* message,
158 const base::string16& source,
159 const base::string16& stack_trace,
160 int32 line_number) {
161 extensions::StackTrace result;
162 std::vector<base::string16> pieces;
163 size_t index = 0;
165 if (message->find(base::UTF8ToUTF16(kStackFrameDelimiter)) !=
166 base::string16::npos) {
167 base::SplitStringUsingSubstr(*message,
168 base::UTF8ToUTF16(kStackFrameDelimiter),
169 &pieces);
170 *message = pieces[0];
171 index = 1;
172 } else if (!stack_trace.empty()) {
173 base::SplitStringUsingSubstr(stack_trace,
174 base::UTF8ToUTF16(kStackFrameDelimiter),
175 &pieces);
178 // If we got a stack trace, parse each frame from the text.
179 if (index < pieces.size()) {
180 for (; index < pieces.size(); ++index) {
181 scoped_ptr<extensions::StackFrame> frame =
182 extensions::StackFrame::CreateFromText(pieces[index]);
183 if (frame.get())
184 result.push_back(*frame);
188 if (result.empty()) { // If we don't have a stack trace, mock one up.
189 result.push_back(
190 extensions::StackFrame(line_number,
191 1u, // column number
192 source,
193 base::string16() /* no function name */ ));
196 return result;
199 } // namespace
201 ChromeRenderViewObserver::ChromeRenderViewObserver(
202 content::RenderView* render_view,
203 ChromeRenderProcessObserver* chrome_render_process_observer)
204 : content::RenderViewObserver(render_view),
205 chrome_render_process_observer_(chrome_render_process_observer),
206 translate_helper_(new TranslateHelper(render_view)),
207 phishing_classifier_(NULL),
208 last_indexed_page_id_(-1),
209 capture_timer_(false, false) {
210 const CommandLine& command_line = *CommandLine::ForCurrentProcess();
211 if (!command_line.HasSwitch(switches::kDisableClientSidePhishingDetection))
212 OnSetClientSidePhishingDetection(true);
215 ChromeRenderViewObserver::~ChromeRenderViewObserver() {
218 bool ChromeRenderViewObserver::OnMessageReceived(const IPC::Message& message) {
219 bool handled = true;
220 IPC_BEGIN_MESSAGE_MAP(ChromeRenderViewObserver, message)
221 IPC_MESSAGE_HANDLER(ChromeViewMsg_WebUIJavaScript, OnWebUIJavaScript)
222 IPC_MESSAGE_HANDLER(ChromeViewMsg_JavaScriptStressTestControl,
223 OnJavaScriptStressTestControl)
224 IPC_MESSAGE_HANDLER(ChromeViewMsg_SetClientSidePhishingDetection,
225 OnSetClientSidePhishingDetection)
226 IPC_MESSAGE_HANDLER(ChromeViewMsg_SetVisuallyDeemphasized,
227 OnSetVisuallyDeemphasized)
228 IPC_MESSAGE_HANDLER(ChromeViewMsg_RequestThumbnailForContextNode,
229 OnRequestThumbnailForContextNode)
230 IPC_MESSAGE_HANDLER(ChromeViewMsg_GetFPS, OnGetFPS)
231 #if defined(OS_ANDROID)
232 IPC_MESSAGE_HANDLER(ChromeViewMsg_UpdateTopControlsState,
233 OnUpdateTopControlsState)
234 IPC_MESSAGE_HANDLER(ChromeViewMsg_RetrieveWebappInformation,
235 OnRetrieveWebappInformation)
236 #endif
237 IPC_MESSAGE_HANDLER(ChromeViewMsg_SetWindowFeatures, OnSetWindowFeatures)
238 IPC_MESSAGE_UNHANDLED(handled = false)
239 IPC_END_MESSAGE_MAP()
241 return handled;
244 void ChromeRenderViewObserver::OnWebUIJavaScript(
245 const base::string16& frame_xpath,
246 const base::string16& jscript,
247 int id,
248 bool notify_result) {
249 webui_javascript_.reset(new WebUIJavaScript());
250 webui_javascript_->frame_xpath = frame_xpath;
251 webui_javascript_->jscript = jscript;
252 webui_javascript_->id = id;
253 webui_javascript_->notify_result = notify_result;
256 void ChromeRenderViewObserver::OnJavaScriptStressTestControl(int cmd,
257 int param) {
258 if (cmd == kJavaScriptStressTestSetStressRunType) {
259 v8::Testing::SetStressRunType(static_cast<v8::Testing::StressType>(param));
260 } else if (cmd == kJavaScriptStressTestPrepareStressRun) {
261 v8::Testing::PrepareStressRun(param);
265 #if defined(OS_ANDROID)
266 void ChromeRenderViewObserver::OnUpdateTopControlsState(
267 content::TopControlsState constraints,
268 content::TopControlsState current,
269 bool animate) {
270 render_view()->UpdateTopControlsState(constraints, current, animate);
273 void ChromeRenderViewObserver::OnRetrieveWebappInformation(
274 const GURL& expected_url) {
275 WebFrame* main_frame = render_view()->GetWebView()->mainFrame();
276 WebDocument document =
277 main_frame ? main_frame->document() : WebDocument();
279 WebElement head = document.isNull() ? WebElement() : document.head();
280 GURL document_url = document.isNull() ? GURL() : GURL(document.url());
282 // Make sure we're checking the right page.
283 bool success = document_url == expected_url;
285 bool is_mobile_webapp_capable = false;
286 bool is_apple_mobile_webapp_capable = false;
288 // Search the DOM for the webapp <meta> tags.
289 if (!head.isNull()) {
290 WebNodeList children = head.childNodes();
291 for (unsigned i = 0; i < children.length(); ++i) {
292 WebNode child = children.item(i);
293 if (!child.isElementNode())
294 continue;
295 WebElement elem = child.to<WebElement>();
297 if (elem.hasTagName("meta") && elem.hasAttribute("name")) {
298 std::string name = elem.getAttribute("name").utf8();
299 WebString content = elem.getAttribute("content");
300 if (LowerCaseEqualsASCII(content, "yes")) {
301 if (name == "mobile-web-app-capable") {
302 is_mobile_webapp_capable = true;
303 } else if (name == "apple-mobile-web-app-capable") {
304 is_apple_mobile_webapp_capable = true;
309 } else {
310 success = false;
313 bool is_only_apple_mobile_webapp_capable =
314 is_apple_mobile_webapp_capable && !is_mobile_webapp_capable;
315 if (main_frame && is_only_apple_mobile_webapp_capable) {
316 blink::WebConsoleMessage message(
317 blink::WebConsoleMessage::LevelWarning,
318 "<meta name=\"apple-mobile-web-app-capable\" content=\"yes\"> is "
319 "deprecated. Please include <meta name=\"mobile-web-app-capable\" "
320 "content=\"yes\"> - "
321 "http://developers.google.com/chrome/mobile/docs/installtohomescreen");
322 main_frame->addMessageToConsole(message);
325 Send(new ChromeViewHostMsg_DidRetrieveWebappInformation(
326 routing_id(),
327 success,
328 is_mobile_webapp_capable,
329 is_apple_mobile_webapp_capable,
330 expected_url));
332 #endif
334 void ChromeRenderViewObserver::OnSetWindowFeatures(
335 const WebWindowFeatures& window_features) {
336 render_view()->GetWebView()->setWindowFeatures(window_features);
339 void ChromeRenderViewObserver::Navigate(const GURL& url) {
340 // Execute cache clear operations that were postponed until a navigation
341 // event (including tab reload).
342 if (chrome_render_process_observer_)
343 chrome_render_process_observer_->ExecutePendingClearCache();
346 void ChromeRenderViewObserver::OnSetClientSidePhishingDetection(
347 bool enable_phishing_detection) {
348 #if defined(FULL_SAFE_BROWSING) && !defined(OS_CHROMEOS)
349 phishing_classifier_ = enable_phishing_detection ?
350 safe_browsing::PhishingClassifierDelegate::Create(
351 render_view(), NULL) :
352 NULL;
353 #endif
356 void ChromeRenderViewObserver::OnSetVisuallyDeemphasized(bool deemphasized) {
357 bool already_deemphasized = !!dimmed_color_overlay_.get();
358 if (already_deemphasized == deemphasized)
359 return;
361 if (deemphasized) {
362 // 70% opaque grey.
363 SkColor greyish = SkColorSetARGB(178, 0, 0, 0);
364 dimmed_color_overlay_.reset(
365 new WebViewColorOverlay(render_view(), greyish));
366 } else {
367 dimmed_color_overlay_.reset();
371 void ChromeRenderViewObserver::OnRequestThumbnailForContextNode(
372 int thumbnail_min_area_pixels, gfx::Size thumbnail_max_size_pixels) {
373 WebNode context_node = render_view()->GetContextMenuNode();
374 SkBitmap thumbnail;
375 gfx::Size original_size;
376 if (!context_node.isNull() && context_node.isElementNode()) {
377 blink::WebImage image = context_node.to<WebElement>().imageContents();
378 original_size = image.size();
379 thumbnail = Downscale(image,
380 thumbnail_min_area_pixels,
381 thumbnail_max_size_pixels);
383 Send(new ChromeViewHostMsg_RequestThumbnailForContextNode_ACK(
384 routing_id(), thumbnail, original_size));
387 void ChromeRenderViewObserver::OnGetFPS() {
388 float fps = (render_view()->GetFilteredTimePerFrame() > 0.0f)?
389 1.0f / render_view()->GetFilteredTimePerFrame() : 0.0f;
390 Send(new ChromeViewHostMsg_FPS(routing_id(), fps));
393 void ChromeRenderViewObserver::DidStartLoading() {
394 if ((render_view()->GetEnabledBindings() & content::BINDINGS_POLICY_WEB_UI) &&
395 webui_javascript_.get()) {
396 render_view()->EvaluateScript(webui_javascript_->frame_xpath,
397 webui_javascript_->jscript,
398 webui_javascript_->id,
399 webui_javascript_->notify_result);
400 webui_javascript_.reset();
404 void ChromeRenderViewObserver::DidStopLoading() {
405 WebFrame* main_frame = render_view()->GetWebView()->mainFrame();
406 GURL osd_url = main_frame->document().openSearchDescriptionURL();
407 if (!osd_url.is_empty()) {
408 Send(new ChromeViewHostMsg_PageHasOSDD(
409 routing_id(), render_view()->GetPageId(), osd_url,
410 search_provider::AUTODETECTED_PROVIDER));
413 // Don't capture pages including refresh meta tag.
414 if (HasRefreshMetaTag(main_frame))
415 return;
417 CapturePageInfoLater(
418 render_view()->GetPageId(),
419 false, // preliminary_capture
420 base::TimeDelta::FromMilliseconds(
421 render_view()->GetContentStateImmediately() ?
422 0 : kDelayForCaptureMs));
425 void ChromeRenderViewObserver::DidCommitProvisionalLoad(
426 WebFrame* frame, bool is_new_navigation) {
427 // Don't capture pages being not new, or including refresh meta tag.
428 if (!is_new_navigation || HasRefreshMetaTag(frame))
429 return;
431 CapturePageInfoLater(
432 render_view()->GetPageId(),
433 true, // preliminary_capture
434 base::TimeDelta::FromMilliseconds(kDelayForForcedCaptureMs));
437 void ChromeRenderViewObserver::DetailedConsoleMessageAdded(
438 const base::string16& message,
439 const base::string16& source,
440 const base::string16& stack_trace_string,
441 int32 line_number,
442 int32 severity_level) {
443 base::string16 trimmed_message = message;
444 extensions::StackTrace stack_trace = GetStackTraceFromMessage(
445 &trimmed_message,
446 source,
447 stack_trace_string,
448 line_number);
449 Send(new ChromeViewHostMsg_DetailedConsoleMessageAdded(routing_id(),
450 trimmed_message,
451 source,
452 stack_trace,
453 severity_level));
456 void ChromeRenderViewObserver::CapturePageInfoLater(int page_id,
457 bool preliminary_capture,
458 base::TimeDelta delay) {
459 capture_timer_.Start(
460 FROM_HERE,
461 delay,
462 base::Bind(&ChromeRenderViewObserver::CapturePageInfo,
463 base::Unretained(this),
464 page_id,
465 preliminary_capture));
468 void ChromeRenderViewObserver::CapturePageInfo(int page_id,
469 bool preliminary_capture) {
470 // If |page_id| is obsolete, we should stop indexing and capturing a page.
471 if (render_view()->GetPageId() != page_id)
472 return;
474 if (!render_view()->GetWebView())
475 return;
477 WebFrame* main_frame = render_view()->GetWebView()->mainFrame();
478 if (!main_frame)
479 return;
481 // Don't index/capture pages that are in view source mode.
482 if (main_frame->isViewSourceModeEnabled())
483 return;
485 // Don't index/capture pages that failed to load. This only checks the top
486 // level frame so the thumbnail may contain a frame that failed to load.
487 WebDataSource* ds = main_frame->dataSource();
488 if (ds && ds->hasUnreachableURL())
489 return;
491 // Don't index/capture pages that are being prerendered.
492 if (prerender::PrerenderHelper::IsPrerendering(
493 render_view()->GetMainRenderFrame())) {
494 return;
497 // Retrieve the frame's full text (up to kMaxIndexChars), and pass it to the
498 // translate helper for language detection and possible translation.
499 base::string16 contents;
500 base::TimeTicks capture_begin_time = base::TimeTicks::Now();
501 CaptureText(main_frame, &contents);
502 UMA_HISTOGRAM_TIMES(kTranslateCaptureText,
503 base::TimeTicks::Now() - capture_begin_time);
504 if (translate_helper_)
505 translate_helper_->PageCaptured(page_id, contents);
507 // TODO(shess): Is indexing "Full text search" indexing? In that
508 // case more of this can go.
509 // Skip indexing if this is not a new load. Note that the case where
510 // page_id == last_indexed_page_id_ is more complicated, since we need to
511 // reindex if the toplevel URL has changed (such as from a redirect), even
512 // though this may not cause the page id to be incremented.
513 if (page_id < last_indexed_page_id_)
514 return;
516 bool same_page_id = last_indexed_page_id_ == page_id;
517 if (!preliminary_capture)
518 last_indexed_page_id_ = page_id;
520 // Get the URL for this page.
521 GURL url(main_frame->document().url());
522 if (url.is_empty()) {
523 if (!preliminary_capture)
524 last_indexed_url_ = GURL();
525 return;
528 // If the page id is unchanged, check whether the URL (ignoring fragments)
529 // has changed. If so, we need to reindex. Otherwise, assume this is a
530 // reload, in-page navigation, or some other load type where we don't want to
531 // reindex. Note: subframe navigations after onload increment the page id,
532 // so these will trigger a reindex.
533 GURL stripped_url(StripRef(url));
534 if (same_page_id && stripped_url == last_indexed_url_)
535 return;
537 if (!preliminary_capture)
538 last_indexed_url_ = stripped_url;
540 TRACE_EVENT0("renderer", "ChromeRenderViewObserver::CapturePageInfo");
542 #if defined(FULL_SAFE_BROWSING)
543 // Will swap out the string.
544 if (phishing_classifier_)
545 phishing_classifier_->PageCaptured(&contents, preliminary_capture);
546 #endif
549 void ChromeRenderViewObserver::CaptureText(WebFrame* frame,
550 base::string16* contents) {
551 contents->clear();
552 if (!frame)
553 return;
555 #ifdef TIME_TEXT_RETRIEVAL
556 double begin = time_util::GetHighResolutionTimeNow();
557 #endif
559 // get the contents of the frame
560 *contents = frame->contentAsText(kMaxIndexChars);
562 #ifdef TIME_TEXT_RETRIEVAL
563 double end = time_util::GetHighResolutionTimeNow();
564 char buf[128];
565 sprintf_s(buf, "%d chars retrieved for indexing in %gms\n",
566 contents.size(), (end - begin)*1000);
567 OutputDebugStringA(buf);
568 #endif
570 // When the contents are clipped to the maximum, we don't want to have a
571 // partial word indexed at the end that might have been clipped. Therefore,
572 // terminate the string at the last space to ensure no words are clipped.
573 if (contents->size() == kMaxIndexChars) {
574 size_t last_space_index = contents->find_last_of(base::kWhitespaceUTF16);
575 if (last_space_index == base::string16::npos)
576 return; // don't index if we got a huge block of text with no spaces
577 contents->resize(last_space_index);
581 bool ChromeRenderViewObserver::HasRefreshMetaTag(WebFrame* frame) {
582 if (!frame)
583 return false;
584 WebElement head = frame->document().head();
585 if (head.isNull() || !head.hasChildNodes())
586 return false;
588 const WebString tag_name(base::ASCIIToUTF16("meta"));
589 const WebString attribute_name(base::ASCIIToUTF16("http-equiv"));
591 WebNodeList children = head.childNodes();
592 for (size_t i = 0; i < children.length(); ++i) {
593 WebNode node = children.item(i);
594 if (!node.isElementNode())
595 continue;
596 WebElement element = node.to<WebElement>();
597 if (!element.hasTagName(tag_name))
598 continue;
599 WebString value = element.getAttribute(attribute_name);
600 if (value.isNull() || !LowerCaseEqualsASCII(value, "refresh"))
601 continue;
602 return true;
604 return false;