Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / chrome / renderer / chrome_render_view_observer.cc
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.
4
5 #include "chrome/renderer/chrome_render_view_observer.h"
6
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"
55
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;
77
78 // Delay in milliseconds that we'll wait before capturing the page contents
79 // and thumbnail.
80 static const int kDelayForCaptureMs = 500;
81
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;
87
88 // define to write the time necessary for thumbnail/DOM text retrieval,
89 // respectively, into the system debug log
90 // #define TIME_TEXT_RETRIEVAL
91
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;
95
96 // Constants for UMA statistic collection.
97 static const char kTranslateCaptureText[] = "Translate.CaptureText";
98
99 namespace {
100
101 GURL StripRef(const GURL& url) {
102   GURL::Replacements replacements;
103   replacements.ClearRef();
104   return url.ReplaceComponents(replacements);
105 }
106
107 // The delimiter for a stack trace provided by WebKit.
108 const char kStackFrameDelimiter[] = "\n    at ";
109
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;
128
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);
140   }
141
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);
149     }
150   }
151
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 */ ));
158   }
159
160   return result;
161 }
162
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());
177
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;
195           }
196         }
197       }
198     }
199   }
200
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;
207   }
208
209   return success;
210 }
211 #endif
212
213 }  // namespace
214
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);
227 }
228
229 ChromeRenderViewObserver::~ChromeRenderViewObserver() {
230 }
231
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_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)
248 #endif
249     IPC_MESSAGE_HANDLER(ChromeViewMsg_SetWindowFeatures, OnSetWindowFeatures)
250     IPC_MESSAGE_UNHANDLED(handled = false)
251   IPC_END_MESSAGE_MAP()
252
253   return handled;
254 }
255
256 void ChromeRenderViewObserver::OnWebUIJavaScript(
257     const base::string16& javascript) {
258   webui_javascript_ = javascript;
259 }
260
261 #if defined(OS_ANDROID)
262 void ChromeRenderViewObserver::OnUpdateTopControlsState(
263     content::TopControlsState constraints,
264     content::TopControlsState current,
265     bool animate) {
266   render_view()->UpdateTopControlsState(constraints, current, animate);
267 }
268
269 void ChromeRenderViewObserver::OnRetrieveWebappInformation(
270     const GURL& expected_url) {
271   WebFrame* main_frame = render_view()->GetWebView()->mainFrame();
272   bool found_tag;
273   std::string content_str;
274
275   // Search for the "mobile-web-app-capable" tag.
276   bool mobile_parse_success = RetrieveMetaTagContent(
277       main_frame,
278       expected_url,
279       "mobile-web-app-capable",
280       &found_tag,
281       &content_str);
282   bool is_mobile_webapp_capable = mobile_parse_success && found_tag &&
283       LowerCaseEqualsASCII(content_str, "yes");
284
285   // Search for the "apple-mobile-web-app-capable" tag.
286   bool apple_parse_success = RetrieveMetaTagContent(
287       main_frame,
288       expected_url,
289       "apple-mobile-web-app-capable",
290       &found_tag,
291       &content_str);
292   bool is_apple_mobile_webapp_capable = apple_parse_success && found_tag &&
293       LowerCaseEqualsASCII(content_str, "yes");
294
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);
305   }
306
307   Send(new ChromeViewHostMsg_DidRetrieveWebappInformation(
308       routing_id(),
309       mobile_parse_success && apple_parse_success,
310       is_mobile_webapp_capable,
311       is_apple_mobile_webapp_capable,
312       expected_url));
313 }
314
315 void ChromeRenderViewObserver::OnRetrieveMetaTagContent(
316     const GURL& expected_url,
317     const std::string tag_name) {
318   bool found_tag;
319   std::string content_str;
320   bool parsed_successfully = RetrieveMetaTagContent(
321       render_view()->GetWebView()->mainFrame(),
322       expected_url,
323       tag_name,
324       &found_tag,
325       &content_str);
326
327   Send(new ChromeViewHostMsg_DidRetrieveMetaTagContent(
328       routing_id(),
329       parsed_successfully && found_tag,
330       tag_name,
331       content_str,
332       expected_url));
333 }
334 #endif
335
336 void ChromeRenderViewObserver::OnSetWindowFeatures(
337     const WebWindowFeatures& window_features) {
338   render_view()->GetWebView()->setWindowFeatures(window_features);
339 }
340
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);
349 }
350
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) :
357       NULL;
358 #endif
359 }
360
361 void ChromeRenderViewObserver::OnSetVisuallyDeemphasized(bool deemphasized) {
362   bool already_deemphasized = !!dimmed_color_overlay_.get();
363   if (already_deemphasized == deemphasized)
364     return;
365
366   if (deemphasized) {
367     // 70% opaque grey.
368     SkColor greyish = SkColorSetARGB(178, 0, 0, 0);
369     dimmed_color_overlay_.reset(
370         new WebViewColorOverlay(render_view(), greyish));
371   } else {
372     dimmed_color_overlay_.reset();
373   }
374 }
375
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));
380 }
381
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();
387   }
388 }
389
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));
397   }
398
399   // Don't capture pages including refresh meta tag.
400   if (HasRefreshMetaTag(main_frame))
401     return;
402
403   CapturePageInfoLater(
404       render_view()->GetPageId(),
405       false,  // preliminary_capture
406       base::TimeDelta::FromMilliseconds(
407           render_view()->GetContentStateImmediately() ?
408               0 : kDelayForCaptureMs));
409 }
410
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))
415     return;
416
417   CapturePageInfoLater(
418       render_view()->GetPageId(),
419       true,  // preliminary_capture
420       base::TimeDelta::FromMilliseconds(kDelayForForcedCaptureMs));
421 }
422
423 void ChromeRenderViewObserver::DetailedConsoleMessageAdded(
424     const base::string16& message,
425     const base::string16& source,
426     const base::string16& stack_trace_string,
427     int32 line_number,
428     int32 severity_level) {
429   base::string16 trimmed_message = message;
430   extensions::StackTrace stack_trace = GetStackTraceFromMessage(
431       &trimmed_message,
432       source,
433       stack_trace_string,
434       line_number);
435   Send(new ChromeViewHostMsg_DetailedConsoleMessageAdded(routing_id(),
436                                                          trimmed_message,
437                                                          source,
438                                                          stack_trace,
439                                                          severity_level));
440 }
441
442 void ChromeRenderViewObserver::CapturePageInfoLater(int page_id,
443                                                     bool preliminary_capture,
444                                                     base::TimeDelta delay) {
445   capture_timer_.Start(
446       FROM_HERE,
447       delay,
448       base::Bind(&ChromeRenderViewObserver::CapturePageInfo,
449                  base::Unretained(this),
450                  page_id,
451                  preliminary_capture));
452 }
453
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)
458     return;
459
460   if (!render_view()->GetWebView())
461     return;
462
463   WebFrame* main_frame = render_view()->GetWebView()->mainFrame();
464   if (!main_frame)
465     return;
466
467   // Don't index/capture pages that are in view source mode.
468   if (main_frame->isViewSourceModeEnabled())
469     return;
470
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())
475     return;
476
477   // Don't index/capture pages that are being prerendered.
478   if (prerender::PrerenderHelper::IsPrerendering(
479           render_view()->GetMainRenderFrame())) {
480     return;
481   }
482
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);
492
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_)
500     return;
501
502   bool same_page_id = last_indexed_page_id_ == page_id;
503   if (!preliminary_capture)
504     last_indexed_page_id_ = page_id;
505
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();
511     return;
512   }
513
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_)
521     return;
522
523   if (!preliminary_capture)
524     last_indexed_url_ = stripped_url;
525
526   TRACE_EVENT0("renderer", "ChromeRenderViewObserver::CapturePageInfo");
527
528 #if defined(FULL_SAFE_BROWSING)
529   // Will swap out the string.
530   if (phishing_classifier_)
531     phishing_classifier_->PageCaptured(&contents, preliminary_capture);
532 #endif
533 }
534
535 void ChromeRenderViewObserver::CaptureText(WebFrame* frame,
536                                            base::string16* contents) {
537   contents->clear();
538   if (!frame)
539     return;
540
541 #ifdef TIME_TEXT_RETRIEVAL
542   double begin = time_util::GetHighResolutionTimeNow();
543 #endif
544
545   // get the contents of the frame
546   *contents = frame->contentAsText(kMaxIndexChars);
547
548 #ifdef TIME_TEXT_RETRIEVAL
549   double end = time_util::GetHighResolutionTimeNow();
550   char buf[128];
551   sprintf_s(buf, "%d chars retrieved for indexing in %gms\n",
552             contents.size(), (end - begin)*1000);
553   OutputDebugStringA(buf);
554 #endif
555
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);
564   }
565 }
566
567 bool ChromeRenderViewObserver::HasRefreshMetaTag(WebFrame* frame) {
568   if (!frame)
569     return false;
570   WebElement head = frame->document().head();
571   if (head.isNull() || !head.hasChildNodes())
572     return false;
573
574   const WebString tag_name(base::ASCIIToUTF16("meta"));
575   const WebString attribute_name(base::ASCIIToUTF16("http-equiv"));
576
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())
581       continue;
582     WebElement element = node.to<WebElement>();
583     if (!element.hasTagName(tag_name))
584       continue;
585     WebString value = element.getAttribute(attribute_name);
586     if (value.isNull() || !LowerCaseEqualsASCII(value, "refresh"))
587       continue;
588     return true;
589   }
590   return false;
591 }