- add sources.
[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/content_settings_observer.h"
23 #include "chrome/renderer/extensions/dispatcher.h"
24 #include "chrome/renderer/external_host_bindings.h"
25 #include "chrome/renderer/prerender/prerender_helper.h"
26 #include "chrome/renderer/safe_browsing/phishing_classifier_delegate.h"
27 #include "chrome/renderer/translate/translate_helper.h"
28 #include "chrome/renderer/webview_color_overlay.h"
29 #include "content/public/common/bindings_policy.h"
30 #include "content/public/renderer/content_renderer_client.h"
31 #include "content/public/renderer/render_view.h"
32 #include "extensions/common/constants.h"
33 #include "extensions/common/stack_frame.h"
34 #include "net/base/data_url.h"
35 #include "skia/ext/image_operations.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/WebFrame.h"
48 #include "third_party/WebKit/public/web/WebInputEvent.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/size.h"
55 #include "ui/gfx/size_f.h"
56 #include "ui/gfx/skbitmap_operations.h"
57 #include "v8/include/v8-testing.h"
58 #include "webkit/glue/webkit_glue.h"
59
60 using base::string16;
61 using extensions::APIPermission;
62 using WebKit::WebAXObject;
63 using WebKit::WebCString;
64 using WebKit::WebDataSource;
65 using WebKit::WebDocument;
66 using WebKit::WebElement;
67 using WebKit::WebFrame;
68 using WebKit::WebGestureEvent;
69 using WebKit::WebIconURL;
70 using WebKit::WebNode;
71 using WebKit::WebNodeList;
72 using WebKit::WebRect;
73 using WebKit::WebSecurityOrigin;
74 using WebKit::WebSize;
75 using WebKit::WebString;
76 using WebKit::WebTouchEvent;
77 using WebKit::WebURL;
78 using WebKit::WebURLRequest;
79 using WebKit::WebView;
80 using WebKit::WebVector;
81 using WebKit::WebWindowFeatures;
82
83 // Delay in milliseconds that we'll wait before capturing the page contents
84 // and thumbnail.
85 static const int kDelayForCaptureMs = 500;
86
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
90 // delay.
91 static const int kDelayForForcedCaptureMs = 6000;
92
93 // define to write the time necessary for thumbnail/DOM text retrieval,
94 // respectively, into the system debug log
95 // #define TIME_TEXT_RETRIEVAL
96
97 // maximum number of characters in the document to index, any text beyond this
98 // point will be clipped
99 static const size_t kMaxIndexChars = 65535;
100
101 // Constants for UMA statistic collection.
102 static const char kWWWDotGoogleDotCom[] = "www.google.com";
103 static const char kMailDotGoogleDotCom[] = "mail.google.com";
104 static const char kPlusDotGoogleDotCom[] = "plus.google.com";
105 static const char kDocsDotGoogleDotCom[] = "docs.google.com";
106 static const char kSitesDotGoogleDotCom[] = "sites.google.com";
107 static const char kPicasawebDotGoogleDotCom[] = "picasaweb.google.com";
108 static const char kCodeDotGoogleDotCom[] = "code.google.com";
109 static const char kGroupsDotGoogleDotCom[] = "groups.google.com";
110 static const char kMapsDotGoogleDotCom[] = "maps.google.com";
111 static const char kWWWDotYoutubeDotCom[] = "www.youtube.com";
112 static const char kDotGoogleUserContentDotCom[] = ".googleusercontent.com";
113 static const char kGoogleReaderPathPrefix[] = "/reader/";
114 static const char kGoogleSupportPathPrefix[] = "/support/";
115 static const char kGoogleIntlPathPrefix[] = "/intl/";
116 static const char kDotJS[] = ".js";
117 static const char kDotCSS[] = ".css";
118 static const char kDotSWF[] = ".swf";
119 static const char kDotHTML[] = ".html";
120 static const char kTranslateCaptureText[] = "Translate.CaptureText";
121 enum {
122   INSECURE_CONTENT_DISPLAY = 0,
123   INSECURE_CONTENT_DISPLAY_HOST_GOOGLE,
124   INSECURE_CONTENT_DISPLAY_HOST_WWW_GOOGLE,
125   INSECURE_CONTENT_DISPLAY_HTML,
126   INSECURE_CONTENT_RUN,
127   INSECURE_CONTENT_RUN_HOST_GOOGLE,
128   INSECURE_CONTENT_RUN_HOST_WWW_GOOGLE,
129   INSECURE_CONTENT_RUN_TARGET_YOUTUBE,
130   INSECURE_CONTENT_RUN_JS,
131   INSECURE_CONTENT_RUN_CSS,
132   INSECURE_CONTENT_RUN_SWF,
133   INSECURE_CONTENT_DISPLAY_HOST_YOUTUBE,
134   INSECURE_CONTENT_RUN_HOST_YOUTUBE,
135   INSECURE_CONTENT_RUN_HOST_GOOGLEUSERCONTENT,
136   INSECURE_CONTENT_DISPLAY_HOST_MAIL_GOOGLE,
137   INSECURE_CONTENT_RUN_HOST_MAIL_GOOGLE,
138   INSECURE_CONTENT_DISPLAY_HOST_PLUS_GOOGLE,
139   INSECURE_CONTENT_RUN_HOST_PLUS_GOOGLE,
140   INSECURE_CONTENT_DISPLAY_HOST_DOCS_GOOGLE,
141   INSECURE_CONTENT_RUN_HOST_DOCS_GOOGLE,
142   INSECURE_CONTENT_DISPLAY_HOST_SITES_GOOGLE,
143   INSECURE_CONTENT_RUN_HOST_SITES_GOOGLE,
144   INSECURE_CONTENT_DISPLAY_HOST_PICASAWEB_GOOGLE,
145   INSECURE_CONTENT_RUN_HOST_PICASAWEB_GOOGLE,
146   INSECURE_CONTENT_DISPLAY_HOST_GOOGLE_READER,
147   INSECURE_CONTENT_RUN_HOST_GOOGLE_READER,
148   INSECURE_CONTENT_DISPLAY_HOST_CODE_GOOGLE,
149   INSECURE_CONTENT_RUN_HOST_CODE_GOOGLE,
150   INSECURE_CONTENT_DISPLAY_HOST_GROUPS_GOOGLE,
151   INSECURE_CONTENT_RUN_HOST_GROUPS_GOOGLE,
152   INSECURE_CONTENT_DISPLAY_HOST_MAPS_GOOGLE,
153   INSECURE_CONTENT_RUN_HOST_MAPS_GOOGLE,
154   INSECURE_CONTENT_DISPLAY_HOST_GOOGLE_SUPPORT,
155   INSECURE_CONTENT_RUN_HOST_GOOGLE_SUPPORT,
156   INSECURE_CONTENT_DISPLAY_HOST_GOOGLE_INTL,
157   INSECURE_CONTENT_RUN_HOST_GOOGLE_INTL,
158   INSECURE_CONTENT_NUM_EVENTS
159 };
160
161 // Constants for mixed-content blocking.
162 static const char kGoogleDotCom[] = "google.com";
163
164 static bool isHostInDomain(const std::string& host, const std::string& domain) {
165   return (EndsWith(host, domain, false) &&
166           (host.length() == domain.length() ||
167            (host.length() > domain.length() &&
168             host[host.length() - domain.length() - 1] == '.')));
169 }
170
171 namespace {
172
173 GURL StripRef(const GURL& url) {
174   GURL::Replacements replacements;
175   replacements.ClearRef();
176   return url.ReplaceComponents(replacements);
177 }
178
179 // If the source image is null or occupies less area than
180 // |thumbnail_min_area_pixels|, we return the image unmodified.  Otherwise, we
181 // scale down the image so that the width and height do not exceed
182 // |thumbnail_max_size_pixels|, preserving the original aspect ratio.
183 SkBitmap Downscale(WebKit::WebImage image,
184                    int thumbnail_min_area_pixels,
185                    gfx::Size thumbnail_max_size_pixels) {
186   if (image.isNull())
187     return SkBitmap();
188
189   gfx::Size image_size = image.size();
190
191   if (image_size.GetArea() < thumbnail_min_area_pixels)
192     return image.getSkBitmap();
193
194   if (image_size.width() <= thumbnail_max_size_pixels.width() &&
195       image_size.height() <= thumbnail_max_size_pixels.height())
196     return image.getSkBitmap();
197
198   gfx::SizeF scaled_size = image_size;
199
200   if (scaled_size.width() > thumbnail_max_size_pixels.width()) {
201     scaled_size.Scale(thumbnail_max_size_pixels.width() / scaled_size.width());
202   }
203
204   if (scaled_size.height() > thumbnail_max_size_pixels.height()) {
205     scaled_size.Scale(
206         thumbnail_max_size_pixels.height() / scaled_size.height());
207   }
208
209   return skia::ImageOperations::Resize(image.getSkBitmap(),
210                                        skia::ImageOperations::RESIZE_GOOD,
211                                        static_cast<int>(scaled_size.width()),
212                                        static_cast<int>(scaled_size.height()));
213 }
214
215 // The delimiter for a stack trace provided by WebKit.
216 const char kStackFrameDelimiter[] = "\n    at ";
217
218 // Get a stack trace from a WebKit console message.
219 // There are three possible scenarios:
220 // 1. WebKit gives us a stack trace in |stack_trace|.
221 // 2. The stack trace is embedded in the error |message| by an internal
222 //    script. This will be more useful than |stack_trace|, since |stack_trace|
223 //    will include the internal bindings trace, instead of a developer's code.
224 // 3. No stack trace is included. In this case, we should mock one up from
225 //    the given line number and source.
226 // |message| will be populated with the error message only (i.e., will not
227 // include any stack trace).
228 extensions::StackTrace GetStackTraceFromMessage(string16* message,
229                                                 const string16& source,
230                                                 const string16& stack_trace,
231                                                 int32 line_number) {
232   extensions::StackTrace result;
233   std::vector<base::string16> pieces;
234   size_t index = 0;
235
236   if (message->find(base::UTF8ToUTF16(kStackFrameDelimiter)) !=
237           string16::npos) {
238     base::SplitStringUsingSubstr(*message,
239                                  base::UTF8ToUTF16(kStackFrameDelimiter),
240                                  &pieces);
241     *message = pieces[0];
242     index = 1;
243   } else if (!stack_trace.empty()) {
244     base::SplitStringUsingSubstr(stack_trace,
245                                  base::UTF8ToUTF16(kStackFrameDelimiter),
246                                  &pieces);
247   }
248
249   // If we got a stack trace, parse each frame from the text.
250   if (index < pieces.size()) {
251     for (; index < pieces.size(); ++index) {
252       scoped_ptr<extensions::StackFrame> frame =
253           extensions::StackFrame::CreateFromText(pieces[index]);
254       if (frame.get())
255         result.push_back(*frame);
256     }
257   }
258
259   if (result.empty()) {  // If we don't have a stack trace, mock one up.
260     result.push_back(
261         extensions::StackFrame(line_number,
262                                1u,  // column number
263                                source,
264                                EmptyString16() /* no function name */ ));
265   }
266
267   return result;
268 }
269
270 }  // namespace
271
272 ChromeRenderViewObserver::ChromeRenderViewObserver(
273     content::RenderView* render_view,
274     ContentSettingsObserver* content_settings,
275     ChromeRenderProcessObserver* chrome_render_process_observer,
276     extensions::Dispatcher* extension_dispatcher)
277     : content::RenderViewObserver(render_view),
278       chrome_render_process_observer_(chrome_render_process_observer),
279       extension_dispatcher_(extension_dispatcher),
280       content_settings_(content_settings),
281       translate_helper_(new TranslateHelper(render_view)),
282       phishing_classifier_(NULL),
283       last_indexed_page_id_(-1),
284       allow_displaying_insecure_content_(false),
285       allow_running_insecure_content_(false),
286       capture_timer_(false, false) {
287   const CommandLine& command_line = *CommandLine::ForCurrentProcess();
288   render_view->GetWebView()->setPermissionClient(this);
289   if (!command_line.HasSwitch(switches::kDisableClientSidePhishingDetection))
290     OnSetClientSidePhishingDetection(true);
291 }
292
293 ChromeRenderViewObserver::~ChromeRenderViewObserver() {
294 }
295
296 bool ChromeRenderViewObserver::OnMessageReceived(const IPC::Message& message) {
297   bool handled = true;
298   IPC_BEGIN_MESSAGE_MAP(ChromeRenderViewObserver, message)
299     IPC_MESSAGE_HANDLER(ChromeViewMsg_WebUIJavaScript, OnWebUIJavaScript)
300     IPC_MESSAGE_HANDLER(ChromeViewMsg_HandleMessageFromExternalHost,
301                         OnHandleMessageFromExternalHost)
302     IPC_MESSAGE_HANDLER(ChromeViewMsg_JavaScriptStressTestControl,
303                         OnJavaScriptStressTestControl)
304     IPC_MESSAGE_HANDLER(ChromeViewMsg_SetAllowDisplayingInsecureContent,
305                         OnSetAllowDisplayingInsecureContent)
306     IPC_MESSAGE_HANDLER(ChromeViewMsg_SetAllowRunningInsecureContent,
307                         OnSetAllowRunningInsecureContent)
308     IPC_MESSAGE_HANDLER(ChromeViewMsg_SetClientSidePhishingDetection,
309                         OnSetClientSidePhishingDetection)
310     IPC_MESSAGE_HANDLER(ChromeViewMsg_SetVisuallyDeemphasized,
311                         OnSetVisuallyDeemphasized)
312     IPC_MESSAGE_HANDLER(ChromeViewMsg_RequestThumbnailForContextNode,
313                         OnRequestThumbnailForContextNode)
314     IPC_MESSAGE_HANDLER(ChromeViewMsg_GetFPS, OnGetFPS)
315     IPC_MESSAGE_HANDLER(ChromeViewMsg_AddStrictSecurityHost,
316                         OnAddStrictSecurityHost)
317     IPC_MESSAGE_HANDLER(ChromeViewMsg_NPAPINotSupported, OnNPAPINotSupported)
318 #if defined(OS_ANDROID)
319     IPC_MESSAGE_HANDLER(ChromeViewMsg_UpdateTopControlsState,
320                         OnUpdateTopControlsState)
321     IPC_MESSAGE_HANDLER(ChromeViewMsg_RetrieveWebappInformation,
322                         OnRetrieveWebappInformation)
323 #endif
324     IPC_MESSAGE_HANDLER(ChromeViewMsg_SetWindowFeatures, OnSetWindowFeatures)
325     IPC_MESSAGE_UNHANDLED(handled = false)
326   IPC_END_MESSAGE_MAP()
327
328   // Filter only.
329   IPC_BEGIN_MESSAGE_MAP(ChromeRenderViewObserver, message)
330     IPC_MESSAGE_HANDLER(PrerenderMsg_SetIsPrerendering, OnSetIsPrerendering);
331   IPC_END_MESSAGE_MAP()
332
333   return handled;
334 }
335
336 void ChromeRenderViewObserver::OnWebUIJavaScript(
337     const string16& frame_xpath,
338     const string16& jscript,
339     int id,
340     bool notify_result) {
341   webui_javascript_.reset(new WebUIJavaScript());
342   webui_javascript_->frame_xpath = frame_xpath;
343   webui_javascript_->jscript = jscript;
344   webui_javascript_->id = id;
345   webui_javascript_->notify_result = notify_result;
346 }
347
348 void ChromeRenderViewObserver::OnHandleMessageFromExternalHost(
349     const std::string& message,
350     const std::string& origin,
351     const std::string& target) {
352   if (message.empty())
353     return;
354   GetExternalHostBindings()->ForwardMessageFromExternalHost(message, origin,
355                                                             target);
356 }
357
358 void ChromeRenderViewObserver::OnJavaScriptStressTestControl(int cmd,
359                                                              int param) {
360   if (cmd == kJavaScriptStressTestSetStressRunType) {
361     v8::Testing::SetStressRunType(static_cast<v8::Testing::StressType>(param));
362   } else if (cmd == kJavaScriptStressTestPrepareStressRun) {
363     v8::Testing::PrepareStressRun(param);
364   }
365 }
366
367 void ChromeRenderViewObserver::OnSetAllowDisplayingInsecureContent(bool allow) {
368   allow_displaying_insecure_content_ = allow;
369   WebFrame* main_frame = render_view()->GetWebView()->mainFrame();
370   if (main_frame)
371     main_frame->reload();
372 }
373
374 void ChromeRenderViewObserver::OnSetAllowRunningInsecureContent(bool allow) {
375   allow_running_insecure_content_ = allow;
376   OnSetAllowDisplayingInsecureContent(allow);
377 }
378
379 void ChromeRenderViewObserver::OnAddStrictSecurityHost(
380     const std::string& host) {
381   strict_security_hosts_.insert(host);
382 }
383
384 void ChromeRenderViewObserver::OnNPAPINotSupported() {
385 #if defined(USE_AURA) && defined(OS_WIN)
386   content_settings_->BlockNPAPIPlugins();
387 #else
388   NOTREACHED();
389 #endif
390 }
391
392 #if defined(OS_ANDROID)
393 void ChromeRenderViewObserver::OnUpdateTopControlsState(
394     content::TopControlsState constraints,
395     content::TopControlsState current,
396     bool animate) {
397   render_view()->UpdateTopControlsState(constraints, current, animate);
398 }
399
400 void ChromeRenderViewObserver::OnRetrieveWebappInformation(
401     const GURL& expected_url) {
402   WebFrame* main_frame = render_view()->GetWebView()->mainFrame();
403   WebDocument document =
404       main_frame ? main_frame->document() : WebDocument();
405
406   WebElement head = document.isNull() ? WebElement() : document.head();
407   GURL document_url = document.isNull() ? GURL() : GURL(document.url());
408
409   // Make sure we're checking the right page.
410   bool success = document_url == expected_url;
411
412   bool is_mobile_webapp_capable = false;
413   bool is_apple_mobile_webapp_capable = false;
414
415   // Search the DOM for the webapp <meta> tags.
416   if (!head.isNull()) {
417     WebNodeList children = head.childNodes();
418     for (unsigned i = 0; i < children.length(); ++i) {
419       WebNode child = children.item(i);
420       if (!child.isElementNode())
421         continue;
422       WebElement elem = child.to<WebElement>();
423
424       if (elem.hasTagName("meta") && elem.hasAttribute("name")) {
425         std::string name = elem.getAttribute("name").utf8();
426         WebString content = elem.getAttribute("content");
427         if (LowerCaseEqualsASCII(content, "yes")) {
428           if (name == "mobile-web-app-capable") {
429             is_mobile_webapp_capable = true;
430           } else if (name == "apple-mobile-web-app-capable") {
431             is_apple_mobile_webapp_capable = true;
432           }
433         }
434       }
435     }
436   } else {
437     success = false;
438   }
439
440   bool is_only_apple_mobile_webapp_capable =
441       is_apple_mobile_webapp_capable && !is_mobile_webapp_capable;
442   if (main_frame && is_only_apple_mobile_webapp_capable) {
443     WebKit::WebConsoleMessage message(
444         WebKit::WebConsoleMessage::LevelWarning,
445         "<meta name=\"apple-mobile-web-app-capable\" content=\"yes\"> is "
446         "deprecated. Please include <meta name=\"mobile-web-app-capable\" "
447         "content=\"yes\"> - "
448         "http://developers.google.com/chrome/mobile/docs/installtohomescreen");
449     main_frame->addMessageToConsole(message);
450   }
451
452   Send(new ChromeViewHostMsg_DidRetrieveWebappInformation(
453       routing_id(),
454       success,
455       is_mobile_webapp_capable,
456       is_apple_mobile_webapp_capable,
457       expected_url));
458 }
459 #endif
460
461 void ChromeRenderViewObserver::OnSetWindowFeatures(
462     const WebWindowFeatures& window_features) {
463   render_view()->GetWebView()->setWindowFeatures(window_features);
464 }
465
466 void ChromeRenderViewObserver::Navigate(const GURL& url) {
467   // Execute cache clear operations that were postponed until a navigation
468   // event (including tab reload).
469   if (chrome_render_process_observer_)
470     chrome_render_process_observer_->ExecutePendingClearCache();
471 }
472
473 void ChromeRenderViewObserver::OnSetClientSidePhishingDetection(
474     bool enable_phishing_detection) {
475 #if defined(FULL_SAFE_BROWSING) && !defined(OS_CHROMEOS)
476   phishing_classifier_ = enable_phishing_detection ?
477       safe_browsing::PhishingClassifierDelegate::Create(
478           render_view(), NULL) :
479       NULL;
480 #endif
481 }
482
483 void ChromeRenderViewObserver::OnSetVisuallyDeemphasized(bool deemphasized) {
484   bool already_deemphasized = !!dimmed_color_overlay_.get();
485   if (already_deemphasized == deemphasized)
486     return;
487
488   if (deemphasized) {
489     // 70% opaque grey.
490     SkColor greyish = SkColorSetARGB(178, 0, 0, 0);
491     dimmed_color_overlay_.reset(
492         new WebViewColorOverlay(render_view(), greyish));
493   } else {
494     dimmed_color_overlay_.reset();
495   }
496 }
497
498 void ChromeRenderViewObserver::OnRequestThumbnailForContextNode(
499     int thumbnail_min_area_pixels, gfx::Size thumbnail_max_size_pixels) {
500   WebNode context_node = render_view()->GetContextMenuNode();
501   SkBitmap thumbnail;
502   gfx::Size original_size;
503   if (!context_node.isNull() && context_node.isElementNode()) {
504     WebKit::WebImage image = context_node.to<WebElement>().imageContents();
505     original_size = image.size();
506     thumbnail = Downscale(image,
507                           thumbnail_min_area_pixels,
508                           thumbnail_max_size_pixels);
509   }
510   Send(new ChromeViewHostMsg_RequestThumbnailForContextNode_ACK(
511       routing_id(), thumbnail, original_size));
512 }
513
514 void ChromeRenderViewObserver::OnGetFPS() {
515   float fps = (render_view()->GetFilteredTimePerFrame() > 0.0f)?
516       1.0f / render_view()->GetFilteredTimePerFrame() : 0.0f;
517   Send(new ChromeViewHostMsg_FPS(routing_id(), fps));
518 }
519
520 bool ChromeRenderViewObserver::allowDatabase(
521     WebFrame* frame,
522     const WebString& name,
523     const WebString& display_name,
524     unsigned long estimated_size) {
525   return content_settings_->AllowDatabase(
526       frame, name, display_name, estimated_size);
527 }
528
529 bool ChromeRenderViewObserver::allowFileSystem(WebFrame* frame) {
530   return content_settings_->AllowFileSystem(frame);
531 }
532
533 bool ChromeRenderViewObserver::allowImage(WebFrame* frame,
534                                           bool enabled_per_settings,
535                                           const WebURL& image_url) {
536   return content_settings_->AllowImage(frame, enabled_per_settings, image_url);
537 }
538
539 bool ChromeRenderViewObserver::allowIndexedDB(WebFrame* frame,
540                                               const WebString& name,
541                                               const WebSecurityOrigin& origin) {
542   return content_settings_->AllowIndexedDB(frame, name, origin);
543 }
544
545 bool ChromeRenderViewObserver::allowPlugins(WebFrame* frame,
546                                            bool enabled_per_settings) {
547   return content_settings_->AllowPlugins(frame, enabled_per_settings);
548 }
549
550 bool ChromeRenderViewObserver::allowScript(WebFrame* frame,
551                                           bool enabled_per_settings) {
552   return content_settings_->AllowScript(frame, enabled_per_settings);
553 }
554
555 bool ChromeRenderViewObserver::allowScriptFromSource(
556     WebFrame* frame,
557     bool enabled_per_settings,
558     const WebURL& script_url) {
559   return content_settings_->AllowScriptFromSource(frame,
560                                                   enabled_per_settings,
561                                                   script_url);
562 }
563
564 bool ChromeRenderViewObserver::allowStorage(WebFrame* frame, bool local) {
565   return content_settings_->AllowStorage(frame, local);
566 }
567
568 bool ChromeRenderViewObserver::allowReadFromClipboard(WebFrame* frame,
569                                                      bool default_value) {
570   bool allowed = false;
571   // TODO(dcheng): Should we consider a toURL() method on WebSecurityOrigin?
572   Send(new ChromeViewHostMsg_CanTriggerClipboardRead(
573       routing_id(), GURL(frame->document().securityOrigin().toString().utf8()),
574       &allowed));
575   return allowed;
576 }
577
578 bool ChromeRenderViewObserver::allowWriteToClipboard(WebFrame* frame,
579                                                     bool default_value) {
580   bool allowed = false;
581   Send(new ChromeViewHostMsg_CanTriggerClipboardWrite(
582       routing_id(), GURL(frame->document().securityOrigin().toString().utf8()),
583       &allowed));
584   return allowed;
585 }
586
587 bool ChromeRenderViewObserver::allowWebComponents(const WebDocument& document,
588                                                   bool defaultValue) {
589   if (defaultValue)
590     return true;
591
592   WebSecurityOrigin origin = document.securityOrigin();
593   if (EqualsASCII(origin.protocol(), chrome::kChromeUIScheme))
594     return true;
595
596   if (const extensions::Extension* extension = GetExtension(origin)) {
597     if (extension->HasAPIPermission(APIPermission::kExperimental))
598       return true;
599   }
600
601   return false;
602 }
603
604 bool ChromeRenderViewObserver::allowHTMLNotifications(
605     const WebDocument& document) {
606   CommandLine* command_line = CommandLine::ForCurrentProcess();
607   if (command_line->HasSwitch(switches::kDisableHTMLNotifications))
608     return false;
609
610   WebSecurityOrigin origin = document.securityOrigin();
611   const extensions::Extension* extension = GetExtension(origin);
612   return extension && extension->HasAPIPermission(APIPermission::kNotification);
613 }
614
615 bool ChromeRenderViewObserver::allowMutationEvents(const WebDocument& document,
616                                                    bool default_value) {
617   WebSecurityOrigin origin = document.securityOrigin();
618   const extensions::Extension* extension = GetExtension(origin);
619   if (extension && extension->is_platform_app())
620     return false;
621   return default_value;
622 }
623
624 bool ChromeRenderViewObserver::allowPushState(const WebDocument& document) {
625   WebSecurityOrigin origin = document.securityOrigin();
626   const extensions::Extension* extension = GetExtension(origin);
627   return !extension || !extension->is_platform_app();
628 }
629
630 static void SendInsecureContentSignal(int signal) {
631   UMA_HISTOGRAM_ENUMERATION("SSL.InsecureContent", signal,
632                             INSECURE_CONTENT_NUM_EVENTS);
633 }
634
635 bool ChromeRenderViewObserver::allowDisplayingInsecureContent(
636     WebKit::WebFrame* frame,
637     bool allowed_per_settings,
638     const WebKit::WebSecurityOrigin& origin,
639     const WebKit::WebURL& resource_url) {
640   SendInsecureContentSignal(INSECURE_CONTENT_DISPLAY);
641
642   std::string origin_host(origin.host().utf8());
643   GURL frame_gurl(frame->document().url());
644   if (isHostInDomain(origin_host, kGoogleDotCom)) {
645     SendInsecureContentSignal(INSECURE_CONTENT_DISPLAY_HOST_GOOGLE);
646     if (StartsWithASCII(frame_gurl.path(), kGoogleSupportPathPrefix, false)) {
647       SendInsecureContentSignal(INSECURE_CONTENT_DISPLAY_HOST_GOOGLE_SUPPORT);
648     } else if (StartsWithASCII(frame_gurl.path(),
649                                kGoogleIntlPathPrefix,
650                                false)) {
651       SendInsecureContentSignal(INSECURE_CONTENT_DISPLAY_HOST_GOOGLE_INTL);
652     }
653   }
654
655   if (origin_host == kWWWDotGoogleDotCom) {
656     SendInsecureContentSignal(INSECURE_CONTENT_DISPLAY_HOST_WWW_GOOGLE);
657     if (StartsWithASCII(frame_gurl.path(), kGoogleReaderPathPrefix, false))
658       SendInsecureContentSignal(INSECURE_CONTENT_DISPLAY_HOST_GOOGLE_READER);
659   } else if (origin_host == kMailDotGoogleDotCom) {
660     SendInsecureContentSignal(INSECURE_CONTENT_DISPLAY_HOST_MAIL_GOOGLE);
661   } else if (origin_host == kPlusDotGoogleDotCom) {
662     SendInsecureContentSignal(INSECURE_CONTENT_DISPLAY_HOST_PLUS_GOOGLE);
663   } else if (origin_host == kDocsDotGoogleDotCom) {
664     SendInsecureContentSignal(INSECURE_CONTENT_DISPLAY_HOST_DOCS_GOOGLE);
665   } else if (origin_host == kSitesDotGoogleDotCom) {
666     SendInsecureContentSignal(INSECURE_CONTENT_DISPLAY_HOST_SITES_GOOGLE);
667   } else if (origin_host == kPicasawebDotGoogleDotCom) {
668     SendInsecureContentSignal(INSECURE_CONTENT_DISPLAY_HOST_PICASAWEB_GOOGLE);
669   } else if (origin_host == kCodeDotGoogleDotCom) {
670     SendInsecureContentSignal(INSECURE_CONTENT_DISPLAY_HOST_CODE_GOOGLE);
671   } else if (origin_host == kGroupsDotGoogleDotCom) {
672     SendInsecureContentSignal(INSECURE_CONTENT_DISPLAY_HOST_GROUPS_GOOGLE);
673   } else if (origin_host == kMapsDotGoogleDotCom) {
674     SendInsecureContentSignal(INSECURE_CONTENT_DISPLAY_HOST_MAPS_GOOGLE);
675   } else if (origin_host == kWWWDotYoutubeDotCom) {
676     SendInsecureContentSignal(INSECURE_CONTENT_DISPLAY_HOST_YOUTUBE);
677   }
678
679   GURL resource_gurl(resource_url);
680   if (EndsWith(resource_gurl.path(), kDotHTML, false))
681     SendInsecureContentSignal(INSECURE_CONTENT_DISPLAY_HTML);
682
683   if (allowed_per_settings || allow_displaying_insecure_content_)
684     return true;
685
686   Send(new ChromeViewHostMsg_DidBlockDisplayingInsecureContent(routing_id()));
687
688   return false;
689 }
690
691 bool ChromeRenderViewObserver::allowRunningInsecureContent(
692     WebKit::WebFrame* frame,
693     bool allowed_per_settings,
694     const WebKit::WebSecurityOrigin& origin,
695     const WebKit::WebURL& resource_url) {
696   std::string origin_host(origin.host().utf8());
697   GURL frame_gurl(frame->document().url());
698   DCHECK_EQ(frame_gurl.host(), origin_host);
699
700   bool is_google = isHostInDomain(origin_host, kGoogleDotCom);
701   if (is_google) {
702     SendInsecureContentSignal(INSECURE_CONTENT_RUN_HOST_GOOGLE);
703     if (StartsWithASCII(frame_gurl.path(), kGoogleSupportPathPrefix, false)) {
704       SendInsecureContentSignal(INSECURE_CONTENT_RUN_HOST_GOOGLE_SUPPORT);
705     } else if (StartsWithASCII(frame_gurl.path(),
706                                kGoogleIntlPathPrefix,
707                                false)) {
708       SendInsecureContentSignal(INSECURE_CONTENT_RUN_HOST_GOOGLE_INTL);
709     }
710   }
711
712   if (origin_host == kWWWDotGoogleDotCom) {
713     SendInsecureContentSignal(INSECURE_CONTENT_RUN_HOST_WWW_GOOGLE);
714     if (StartsWithASCII(frame_gurl.path(), kGoogleReaderPathPrefix, false))
715       SendInsecureContentSignal(INSECURE_CONTENT_RUN_HOST_GOOGLE_READER);
716   } else if (origin_host == kMailDotGoogleDotCom) {
717     SendInsecureContentSignal(INSECURE_CONTENT_RUN_HOST_MAIL_GOOGLE);
718   } else if (origin_host == kPlusDotGoogleDotCom) {
719     SendInsecureContentSignal(INSECURE_CONTENT_RUN_HOST_PLUS_GOOGLE);
720   } else if (origin_host == kDocsDotGoogleDotCom) {
721     SendInsecureContentSignal(INSECURE_CONTENT_RUN_HOST_DOCS_GOOGLE);
722   } else if (origin_host == kSitesDotGoogleDotCom) {
723     SendInsecureContentSignal(INSECURE_CONTENT_RUN_HOST_SITES_GOOGLE);
724   } else if (origin_host == kPicasawebDotGoogleDotCom) {
725     SendInsecureContentSignal(INSECURE_CONTENT_RUN_HOST_PICASAWEB_GOOGLE);
726   } else if (origin_host == kCodeDotGoogleDotCom) {
727     SendInsecureContentSignal(INSECURE_CONTENT_RUN_HOST_CODE_GOOGLE);
728   } else if (origin_host == kGroupsDotGoogleDotCom) {
729     SendInsecureContentSignal(INSECURE_CONTENT_RUN_HOST_GROUPS_GOOGLE);
730   } else if (origin_host == kMapsDotGoogleDotCom) {
731     SendInsecureContentSignal(INSECURE_CONTENT_RUN_HOST_MAPS_GOOGLE);
732   } else if (origin_host == kWWWDotYoutubeDotCom) {
733     SendInsecureContentSignal(INSECURE_CONTENT_RUN_HOST_YOUTUBE);
734   } else if (EndsWith(origin_host, kDotGoogleUserContentDotCom, false)) {
735     SendInsecureContentSignal(INSECURE_CONTENT_RUN_HOST_GOOGLEUSERCONTENT);
736   }
737
738   GURL resource_gurl(resource_url);
739   if (resource_gurl.host() == kWWWDotYoutubeDotCom)
740     SendInsecureContentSignal(INSECURE_CONTENT_RUN_TARGET_YOUTUBE);
741
742   if (EndsWith(resource_gurl.path(), kDotJS, false))
743     SendInsecureContentSignal(INSECURE_CONTENT_RUN_JS);
744   else if (EndsWith(resource_gurl.path(), kDotCSS, false))
745     SendInsecureContentSignal(INSECURE_CONTENT_RUN_CSS);
746   else if (EndsWith(resource_gurl.path(), kDotSWF, false))
747     SendInsecureContentSignal(INSECURE_CONTENT_RUN_SWF);
748
749   if (!allow_running_insecure_content_ && !allowed_per_settings) {
750     content_settings_->DidNotAllowMixedScript();
751     return false;
752   }
753
754   return true;
755 }
756
757 bool ChromeRenderViewObserver::allowWebGLDebugRendererInfo(WebFrame* frame) {
758   bool allowed = false;
759   Send(new ChromeViewHostMsg_IsWebGLDebugRendererInfoAllowed(
760       routing_id(),
761       GURL(frame->top()->document().securityOrigin().toString().utf8()),
762       &allowed));
763   return allowed;
764 }
765
766 void ChromeRenderViewObserver::didNotAllowPlugins(WebFrame* frame) {
767   content_settings_->DidNotAllowPlugins();
768 }
769
770 void ChromeRenderViewObserver::didNotAllowScript(WebFrame* frame) {
771   content_settings_->DidNotAllowScript();
772 }
773
774 void ChromeRenderViewObserver::OnSetIsPrerendering(bool is_prerendering) {
775   if (is_prerendering) {
776     DCHECK(!prerender::PrerenderHelper::Get(render_view()));
777     // The PrerenderHelper will destroy itself either after recording histograms
778     // or on destruction of the RenderView.
779     new prerender::PrerenderHelper(render_view());
780   }
781 }
782
783 void ChromeRenderViewObserver::DidStartLoading() {
784   if ((render_view()->GetEnabledBindings() & content::BINDINGS_POLICY_WEB_UI) &&
785       webui_javascript_.get()) {
786     render_view()->EvaluateScript(webui_javascript_->frame_xpath,
787                                   webui_javascript_->jscript,
788                                   webui_javascript_->id,
789                                   webui_javascript_->notify_result);
790     webui_javascript_.reset();
791   }
792 }
793
794 void ChromeRenderViewObserver::DidStopLoading() {
795   WebFrame* main_frame = render_view()->GetWebView()->mainFrame();
796   GURL osd_url = main_frame->document().openSearchDescriptionURL();
797   if (!osd_url.is_empty()) {
798     Send(new ChromeViewHostMsg_PageHasOSDD(
799         routing_id(), render_view()->GetPageId(), osd_url,
800         search_provider::AUTODETECTED_PROVIDER));
801   }
802
803   // Don't capture pages including refresh meta tag.
804   if (HasRefreshMetaTag(main_frame))
805     return;
806
807   CapturePageInfoLater(
808       render_view()->GetPageId(),
809       false,  // preliminary_capture
810       base::TimeDelta::FromMilliseconds(
811           render_view()->GetContentStateImmediately() ?
812               0 : kDelayForCaptureMs));
813 }
814
815 void ChromeRenderViewObserver::DidCommitProvisionalLoad(
816     WebFrame* frame, bool is_new_navigation) {
817   // Don't capture pages being not new, or including refresh meta tag.
818   if (!is_new_navigation || HasRefreshMetaTag(frame))
819     return;
820
821   CapturePageInfoLater(
822       render_view()->GetPageId(),
823       true,  // preliminary_capture
824       base::TimeDelta::FromMilliseconds(kDelayForForcedCaptureMs));
825 }
826
827 void ChromeRenderViewObserver::DidClearWindowObject(WebFrame* frame) {
828   if (render_view()->GetEnabledBindings() &
829           content::BINDINGS_POLICY_EXTERNAL_HOST) {
830     GetExternalHostBindings()->BindToJavascript(frame, "externalHost");
831   }
832 }
833
834 void ChromeRenderViewObserver::DidHandleGestureEvent(
835     const WebGestureEvent& event) {
836   if (event.type != WebKit::WebGestureEvent::GestureTap)
837     return;
838
839   WebKit::WebTextInputType text_input_type =
840       render_view()->GetWebView()->textInputInfo().type;
841
842   render_view()->Send(new ChromeViewHostMsg_FocusedNodeTouched(
843       routing_id(),
844       text_input_type != WebKit::WebTextInputTypeNone));
845 }
846
847 void ChromeRenderViewObserver::DetailedConsoleMessageAdded(
848     const base::string16& message,
849     const base::string16& source,
850     const base::string16& stack_trace_string,
851     int32 line_number,
852     int32 severity_level) {
853   string16 trimmed_message = message;
854   extensions::StackTrace stack_trace = GetStackTraceFromMessage(
855       &trimmed_message,
856       source,
857       stack_trace_string,
858       line_number);
859   Send(new ChromeViewHostMsg_DetailedConsoleMessageAdded(routing_id(),
860                                                          trimmed_message,
861                                                          source,
862                                                          stack_trace,
863                                                          severity_level));
864 }
865
866 void ChromeRenderViewObserver::CapturePageInfoLater(int page_id,
867                                                     bool preliminary_capture,
868                                                     base::TimeDelta delay) {
869   capture_timer_.Start(
870       FROM_HERE,
871       delay,
872       base::Bind(&ChromeRenderViewObserver::CapturePageInfo,
873                  base::Unretained(this),
874                  page_id,
875                  preliminary_capture));
876 }
877
878 void ChromeRenderViewObserver::CapturePageInfo(int page_id,
879                                                bool preliminary_capture) {
880   // If |page_id| is obsolete, we should stop indexing and capturing a page.
881   if (render_view()->GetPageId() != page_id)
882     return;
883
884   if (!render_view()->GetWebView())
885     return;
886
887   WebFrame* main_frame = render_view()->GetWebView()->mainFrame();
888   if (!main_frame)
889     return;
890
891   // Don't index/capture pages that are in view source mode.
892   if (main_frame->isViewSourceModeEnabled())
893     return;
894
895   // Don't index/capture pages that failed to load.  This only checks the top
896   // level frame so the thumbnail may contain a frame that failed to load.
897   WebDataSource* ds = main_frame->dataSource();
898   if (ds && ds->hasUnreachableURL())
899     return;
900
901   // Don't index/capture pages that are being prerendered.
902   if (prerender::PrerenderHelper::IsPrerendering(render_view()))
903     return;
904
905   // Retrieve the frame's full text (up to kMaxIndexChars), and pass it to the
906   // translate helper for language detection and possible translation.
907   string16 contents;
908   base::TimeTicks capture_begin_time = base::TimeTicks::Now();
909   CaptureText(main_frame, &contents);
910   UMA_HISTOGRAM_TIMES(kTranslateCaptureText,
911                       base::TimeTicks::Now() - capture_begin_time);
912   if (translate_helper_)
913     translate_helper_->PageCaptured(page_id, contents);
914
915   // Skip indexing if this is not a new load.  Note that the case where
916   // page_id == last_indexed_page_id_ is more complicated, since we need to
917   // reindex if the toplevel URL has changed (such as from a redirect), even
918   // though this may not cause the page id to be incremented.
919   if (page_id < last_indexed_page_id_)
920     return;
921
922   bool same_page_id = last_indexed_page_id_ == page_id;
923   if (!preliminary_capture)
924     last_indexed_page_id_ = page_id;
925
926   // Get the URL for this page.
927   GURL url(main_frame->document().url());
928   if (url.is_empty()) {
929     if (!preliminary_capture)
930       last_indexed_url_ = GURL();
931     return;
932   }
933
934   // If the page id is unchanged, check whether the URL (ignoring fragments)
935   // has changed.  If so, we need to reindex.  Otherwise, assume this is a
936   // reload, in-page navigation, or some other load type where we don't want to
937   // reindex.  Note: subframe navigations after onload increment the page id,
938   // so these will trigger a reindex.
939   GURL stripped_url(StripRef(url));
940   if (same_page_id && stripped_url == last_indexed_url_)
941     return;
942
943   if (!preliminary_capture)
944     last_indexed_url_ = stripped_url;
945
946   TRACE_EVENT0("renderer", "ChromeRenderViewObserver::CapturePageInfo");
947
948   if (contents.size()) {
949     // Send the text to the browser for indexing (the browser might decide not
950     // to index, if the URL is HTTPS for instance).
951     Send(new ChromeViewHostMsg_PageContents(routing_id(), url, contents));
952   }
953
954 #if defined(FULL_SAFE_BROWSING)
955   // Will swap out the string.
956   if (phishing_classifier_)
957     phishing_classifier_->PageCaptured(&contents, preliminary_capture);
958 #endif
959 }
960
961 void ChromeRenderViewObserver::CaptureText(WebFrame* frame,
962                                            string16* contents) {
963   contents->clear();
964   if (!frame)
965     return;
966
967 #ifdef TIME_TEXT_RETRIEVAL
968   double begin = time_util::GetHighResolutionTimeNow();
969 #endif
970
971   // get the contents of the frame
972   *contents = frame->contentAsText(kMaxIndexChars);
973
974 #ifdef TIME_TEXT_RETRIEVAL
975   double end = time_util::GetHighResolutionTimeNow();
976   char buf[128];
977   sprintf_s(buf, "%d chars retrieved for indexing in %gms\n",
978             contents.size(), (end - begin)*1000);
979   OutputDebugStringA(buf);
980 #endif
981
982   // When the contents are clipped to the maximum, we don't want to have a
983   // partial word indexed at the end that might have been clipped. Therefore,
984   // terminate the string at the last space to ensure no words are clipped.
985   if (contents->size() == kMaxIndexChars) {
986     size_t last_space_index = contents->find_last_of(kWhitespaceUTF16);
987     if (last_space_index == std::wstring::npos)
988       return;  // don't index if we got a huge block of text with no spaces
989     contents->resize(last_space_index);
990   }
991 }
992
993 ExternalHostBindings* ChromeRenderViewObserver::GetExternalHostBindings() {
994   if (!external_host_bindings_.get()) {
995     external_host_bindings_.reset(new ExternalHostBindings(
996         render_view(), routing_id()));
997   }
998   return external_host_bindings_.get();
999 }
1000
1001 bool ChromeRenderViewObserver::IsStrictSecurityHost(const std::string& host) {
1002   return (strict_security_hosts_.find(host) != strict_security_hosts_.end());
1003 }
1004
1005 const extensions::Extension* ChromeRenderViewObserver::GetExtension(
1006     const WebSecurityOrigin& origin) const {
1007   if (!EqualsASCII(origin.protocol(), extensions::kExtensionScheme))
1008     return NULL;
1009
1010   const std::string extension_id = origin.host().utf8().data();
1011   if (!extension_dispatcher_->IsExtensionActive(extension_id))
1012     return NULL;
1013
1014   return extension_dispatcher_->extensions()->GetByID(extension_id);
1015 }
1016
1017 bool ChromeRenderViewObserver::HasRefreshMetaTag(WebFrame* frame) {
1018   if (!frame)
1019     return false;
1020   WebElement head = frame->document().head();
1021   if (head.isNull() || !head.hasChildNodes())
1022     return false;
1023
1024   const WebString tag_name(ASCIIToUTF16("meta"));
1025   const WebString attribute_name(ASCIIToUTF16("http-equiv"));
1026
1027   WebNodeList children = head.childNodes();
1028   for (size_t i = 0; i < children.length(); ++i) {
1029     WebNode node = children.item(i);
1030     if (!node.isElementNode())
1031       continue;
1032     WebElement element = node.to<WebElement>();
1033     if (!element.hasTagName(tag_name))
1034       continue;
1035     WebString value = element.getAttribute(attribute_name);
1036     if (value.isNull() || !LowerCaseEqualsASCII(value, "refresh"))
1037       continue;
1038     return true;
1039   }
1040   return false;
1041 }