// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/renderer/chrome_render_view_observer.h" #include "base/bind.h" #include "base/bind_helpers.h" #include "base/command_line.h" #include "base/debug/trace_event.h" #include "base/message_loop/message_loop.h" #include "base/metrics/histogram.h" #include "base/strings/string_split.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" #include "chrome/common/chrome_constants.h" #include "chrome/common/chrome_switches.h" #include "chrome/common/prerender_messages.h" #include "chrome/common/render_messages.h" #include "chrome/common/url_constants.h" #include "chrome/renderer/chrome_render_process_observer.h" #include "chrome/renderer/content_settings_observer.h" #include "chrome/renderer/extensions/dispatcher.h" #include "chrome/renderer/external_host_bindings.h" #include "chrome/renderer/prerender/prerender_helper.h" #include "chrome/renderer/safe_browsing/phishing_classifier_delegate.h" #include "chrome/renderer/translate/translate_helper.h" #include "chrome/renderer/webview_color_overlay.h" #include "content/public/common/bindings_policy.h" #include "content/public/renderer/content_renderer_client.h" #include "content/public/renderer/render_view.h" #include "extensions/common/constants.h" #include "extensions/common/stack_frame.h" #include "net/base/data_url.h" #include "skia/ext/image_operations.h" #include "skia/ext/platform_canvas.h" #include "third_party/WebKit/public/platform/WebCString.h" #include "third_party/WebKit/public/platform/WebRect.h" #include "third_party/WebKit/public/platform/WebSize.h" #include "third_party/WebKit/public/platform/WebString.h" #include "third_party/WebKit/public/platform/WebURLRequest.h" #include "third_party/WebKit/public/platform/WebVector.h" #include "third_party/WebKit/public/web/WebAXObject.h" #include "third_party/WebKit/public/web/WebDataSource.h" #include "third_party/WebKit/public/web/WebDocument.h" #include "third_party/WebKit/public/web/WebElement.h" #include "third_party/WebKit/public/web/WebFrame.h" #include "third_party/WebKit/public/web/WebInputEvent.h" #include "third_party/WebKit/public/web/WebNode.h" #include "third_party/WebKit/public/web/WebNodeList.h" #include "third_party/WebKit/public/web/WebView.h" #include "ui/base/ui_base_switches_util.h" #include "ui/gfx/favicon_size.h" #include "ui/gfx/size.h" #include "ui/gfx/size_f.h" #include "ui/gfx/skbitmap_operations.h" #include "v8/include/v8-testing.h" #include "webkit/glue/webkit_glue.h" using base::string16; using extensions::APIPermission; using WebKit::WebAXObject; using WebKit::WebCString; using WebKit::WebDataSource; using WebKit::WebDocument; using WebKit::WebElement; using WebKit::WebFrame; using WebKit::WebGestureEvent; using WebKit::WebIconURL; using WebKit::WebNode; using WebKit::WebNodeList; using WebKit::WebRect; using WebKit::WebSecurityOrigin; using WebKit::WebSize; using WebKit::WebString; using WebKit::WebTouchEvent; using WebKit::WebURL; using WebKit::WebURLRequest; using WebKit::WebView; using WebKit::WebVector; using WebKit::WebWindowFeatures; // Delay in milliseconds that we'll wait before capturing the page contents // and thumbnail. static const int kDelayForCaptureMs = 500; // Typically, we capture the page data once the page is loaded. // Sometimes, the page never finishes to load, preventing the page capture // To workaround this problem, we always perform a capture after the following // delay. static const int kDelayForForcedCaptureMs = 6000; // define to write the time necessary for thumbnail/DOM text retrieval, // respectively, into the system debug log // #define TIME_TEXT_RETRIEVAL // maximum number of characters in the document to index, any text beyond this // point will be clipped static const size_t kMaxIndexChars = 65535; // Constants for UMA statistic collection. static const char kWWWDotGoogleDotCom[] = "www.google.com"; static const char kMailDotGoogleDotCom[] = "mail.google.com"; static const char kPlusDotGoogleDotCom[] = "plus.google.com"; static const char kDocsDotGoogleDotCom[] = "docs.google.com"; static const char kSitesDotGoogleDotCom[] = "sites.google.com"; static const char kPicasawebDotGoogleDotCom[] = "picasaweb.google.com"; static const char kCodeDotGoogleDotCom[] = "code.google.com"; static const char kGroupsDotGoogleDotCom[] = "groups.google.com"; static const char kMapsDotGoogleDotCom[] = "maps.google.com"; static const char kWWWDotYoutubeDotCom[] = "www.youtube.com"; static const char kDotGoogleUserContentDotCom[] = ".googleusercontent.com"; static const char kGoogleReaderPathPrefix[] = "/reader/"; static const char kGoogleSupportPathPrefix[] = "/support/"; static const char kGoogleIntlPathPrefix[] = "/intl/"; static const char kDotJS[] = ".js"; static const char kDotCSS[] = ".css"; static const char kDotSWF[] = ".swf"; static const char kDotHTML[] = ".html"; static const char kTranslateCaptureText[] = "Translate.CaptureText"; enum { INSECURE_CONTENT_DISPLAY = 0, INSECURE_CONTENT_DISPLAY_HOST_GOOGLE, INSECURE_CONTENT_DISPLAY_HOST_WWW_GOOGLE, INSECURE_CONTENT_DISPLAY_HTML, INSECURE_CONTENT_RUN, INSECURE_CONTENT_RUN_HOST_GOOGLE, INSECURE_CONTENT_RUN_HOST_WWW_GOOGLE, INSECURE_CONTENT_RUN_TARGET_YOUTUBE, INSECURE_CONTENT_RUN_JS, INSECURE_CONTENT_RUN_CSS, INSECURE_CONTENT_RUN_SWF, INSECURE_CONTENT_DISPLAY_HOST_YOUTUBE, INSECURE_CONTENT_RUN_HOST_YOUTUBE, INSECURE_CONTENT_RUN_HOST_GOOGLEUSERCONTENT, INSECURE_CONTENT_DISPLAY_HOST_MAIL_GOOGLE, INSECURE_CONTENT_RUN_HOST_MAIL_GOOGLE, INSECURE_CONTENT_DISPLAY_HOST_PLUS_GOOGLE, INSECURE_CONTENT_RUN_HOST_PLUS_GOOGLE, INSECURE_CONTENT_DISPLAY_HOST_DOCS_GOOGLE, INSECURE_CONTENT_RUN_HOST_DOCS_GOOGLE, INSECURE_CONTENT_DISPLAY_HOST_SITES_GOOGLE, INSECURE_CONTENT_RUN_HOST_SITES_GOOGLE, INSECURE_CONTENT_DISPLAY_HOST_PICASAWEB_GOOGLE, INSECURE_CONTENT_RUN_HOST_PICASAWEB_GOOGLE, INSECURE_CONTENT_DISPLAY_HOST_GOOGLE_READER, INSECURE_CONTENT_RUN_HOST_GOOGLE_READER, INSECURE_CONTENT_DISPLAY_HOST_CODE_GOOGLE, INSECURE_CONTENT_RUN_HOST_CODE_GOOGLE, INSECURE_CONTENT_DISPLAY_HOST_GROUPS_GOOGLE, INSECURE_CONTENT_RUN_HOST_GROUPS_GOOGLE, INSECURE_CONTENT_DISPLAY_HOST_MAPS_GOOGLE, INSECURE_CONTENT_RUN_HOST_MAPS_GOOGLE, INSECURE_CONTENT_DISPLAY_HOST_GOOGLE_SUPPORT, INSECURE_CONTENT_RUN_HOST_GOOGLE_SUPPORT, INSECURE_CONTENT_DISPLAY_HOST_GOOGLE_INTL, INSECURE_CONTENT_RUN_HOST_GOOGLE_INTL, INSECURE_CONTENT_NUM_EVENTS }; // Constants for mixed-content blocking. static const char kGoogleDotCom[] = "google.com"; static bool isHostInDomain(const std::string& host, const std::string& domain) { return (EndsWith(host, domain, false) && (host.length() == domain.length() || (host.length() > domain.length() && host[host.length() - domain.length() - 1] == '.'))); } namespace { GURL StripRef(const GURL& url) { GURL::Replacements replacements; replacements.ClearRef(); return url.ReplaceComponents(replacements); } // If the source image is null or occupies less area than // |thumbnail_min_area_pixels|, we return the image unmodified. Otherwise, we // scale down the image so that the width and height do not exceed // |thumbnail_max_size_pixels|, preserving the original aspect ratio. SkBitmap Downscale(WebKit::WebImage image, int thumbnail_min_area_pixels, gfx::Size thumbnail_max_size_pixels) { if (image.isNull()) return SkBitmap(); gfx::Size image_size = image.size(); if (image_size.GetArea() < thumbnail_min_area_pixels) return image.getSkBitmap(); if (image_size.width() <= thumbnail_max_size_pixels.width() && image_size.height() <= thumbnail_max_size_pixels.height()) return image.getSkBitmap(); gfx::SizeF scaled_size = image_size; if (scaled_size.width() > thumbnail_max_size_pixels.width()) { scaled_size.Scale(thumbnail_max_size_pixels.width() / scaled_size.width()); } if (scaled_size.height() > thumbnail_max_size_pixels.height()) { scaled_size.Scale( thumbnail_max_size_pixels.height() / scaled_size.height()); } return skia::ImageOperations::Resize(image.getSkBitmap(), skia::ImageOperations::RESIZE_GOOD, static_cast(scaled_size.width()), static_cast(scaled_size.height())); } // The delimiter for a stack trace provided by WebKit. const char kStackFrameDelimiter[] = "\n at "; // Get a stack trace from a WebKit console message. // There are three possible scenarios: // 1. WebKit gives us a stack trace in |stack_trace|. // 2. The stack trace is embedded in the error |message| by an internal // script. This will be more useful than |stack_trace|, since |stack_trace| // will include the internal bindings trace, instead of a developer's code. // 3. No stack trace is included. In this case, we should mock one up from // the given line number and source. // |message| will be populated with the error message only (i.e., will not // include any stack trace). extensions::StackTrace GetStackTraceFromMessage(string16* message, const string16& source, const string16& stack_trace, int32 line_number) { extensions::StackTrace result; std::vector pieces; size_t index = 0; if (message->find(base::UTF8ToUTF16(kStackFrameDelimiter)) != string16::npos) { base::SplitStringUsingSubstr(*message, base::UTF8ToUTF16(kStackFrameDelimiter), &pieces); *message = pieces[0]; index = 1; } else if (!stack_trace.empty()) { base::SplitStringUsingSubstr(stack_trace, base::UTF8ToUTF16(kStackFrameDelimiter), &pieces); } // If we got a stack trace, parse each frame from the text. if (index < pieces.size()) { for (; index < pieces.size(); ++index) { scoped_ptr frame = extensions::StackFrame::CreateFromText(pieces[index]); if (frame.get()) result.push_back(*frame); } } if (result.empty()) { // If we don't have a stack trace, mock one up. result.push_back( extensions::StackFrame(line_number, 1u, // column number source, EmptyString16() /* no function name */ )); } return result; } } // namespace ChromeRenderViewObserver::ChromeRenderViewObserver( content::RenderView* render_view, ContentSettingsObserver* content_settings, ChromeRenderProcessObserver* chrome_render_process_observer, extensions::Dispatcher* extension_dispatcher) : content::RenderViewObserver(render_view), chrome_render_process_observer_(chrome_render_process_observer), extension_dispatcher_(extension_dispatcher), content_settings_(content_settings), translate_helper_(new TranslateHelper(render_view)), phishing_classifier_(NULL), last_indexed_page_id_(-1), allow_displaying_insecure_content_(false), allow_running_insecure_content_(false), capture_timer_(false, false) { const CommandLine& command_line = *CommandLine::ForCurrentProcess(); render_view->GetWebView()->setPermissionClient(this); if (!command_line.HasSwitch(switches::kDisableClientSidePhishingDetection)) OnSetClientSidePhishingDetection(true); } ChromeRenderViewObserver::~ChromeRenderViewObserver() { } bool ChromeRenderViewObserver::OnMessageReceived(const IPC::Message& message) { bool handled = true; IPC_BEGIN_MESSAGE_MAP(ChromeRenderViewObserver, message) IPC_MESSAGE_HANDLER(ChromeViewMsg_WebUIJavaScript, OnWebUIJavaScript) IPC_MESSAGE_HANDLER(ChromeViewMsg_HandleMessageFromExternalHost, OnHandleMessageFromExternalHost) IPC_MESSAGE_HANDLER(ChromeViewMsg_JavaScriptStressTestControl, OnJavaScriptStressTestControl) IPC_MESSAGE_HANDLER(ChromeViewMsg_SetAllowDisplayingInsecureContent, OnSetAllowDisplayingInsecureContent) IPC_MESSAGE_HANDLER(ChromeViewMsg_SetAllowRunningInsecureContent, OnSetAllowRunningInsecureContent) IPC_MESSAGE_HANDLER(ChromeViewMsg_SetClientSidePhishingDetection, OnSetClientSidePhishingDetection) IPC_MESSAGE_HANDLER(ChromeViewMsg_SetVisuallyDeemphasized, OnSetVisuallyDeemphasized) IPC_MESSAGE_HANDLER(ChromeViewMsg_RequestThumbnailForContextNode, OnRequestThumbnailForContextNode) IPC_MESSAGE_HANDLER(ChromeViewMsg_GetFPS, OnGetFPS) IPC_MESSAGE_HANDLER(ChromeViewMsg_AddStrictSecurityHost, OnAddStrictSecurityHost) IPC_MESSAGE_HANDLER(ChromeViewMsg_NPAPINotSupported, OnNPAPINotSupported) #if defined(OS_ANDROID) IPC_MESSAGE_HANDLER(ChromeViewMsg_UpdateTopControlsState, OnUpdateTopControlsState) IPC_MESSAGE_HANDLER(ChromeViewMsg_RetrieveWebappInformation, OnRetrieveWebappInformation) #endif IPC_MESSAGE_HANDLER(ChromeViewMsg_SetWindowFeatures, OnSetWindowFeatures) IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP() // Filter only. IPC_BEGIN_MESSAGE_MAP(ChromeRenderViewObserver, message) IPC_MESSAGE_HANDLER(PrerenderMsg_SetIsPrerendering, OnSetIsPrerendering); IPC_END_MESSAGE_MAP() return handled; } void ChromeRenderViewObserver::OnWebUIJavaScript( const string16& frame_xpath, const string16& jscript, int id, bool notify_result) { webui_javascript_.reset(new WebUIJavaScript()); webui_javascript_->frame_xpath = frame_xpath; webui_javascript_->jscript = jscript; webui_javascript_->id = id; webui_javascript_->notify_result = notify_result; } void ChromeRenderViewObserver::OnHandleMessageFromExternalHost( const std::string& message, const std::string& origin, const std::string& target) { if (message.empty()) return; GetExternalHostBindings()->ForwardMessageFromExternalHost(message, origin, target); } void ChromeRenderViewObserver::OnJavaScriptStressTestControl(int cmd, int param) { if (cmd == kJavaScriptStressTestSetStressRunType) { v8::Testing::SetStressRunType(static_cast(param)); } else if (cmd == kJavaScriptStressTestPrepareStressRun) { v8::Testing::PrepareStressRun(param); } } void ChromeRenderViewObserver::OnSetAllowDisplayingInsecureContent(bool allow) { allow_displaying_insecure_content_ = allow; WebFrame* main_frame = render_view()->GetWebView()->mainFrame(); if (main_frame) main_frame->reload(); } void ChromeRenderViewObserver::OnSetAllowRunningInsecureContent(bool allow) { allow_running_insecure_content_ = allow; OnSetAllowDisplayingInsecureContent(allow); } void ChromeRenderViewObserver::OnAddStrictSecurityHost( const std::string& host) { strict_security_hosts_.insert(host); } void ChromeRenderViewObserver::OnNPAPINotSupported() { #if defined(USE_AURA) && defined(OS_WIN) content_settings_->BlockNPAPIPlugins(); #else NOTREACHED(); #endif } #if defined(OS_ANDROID) void ChromeRenderViewObserver::OnUpdateTopControlsState( content::TopControlsState constraints, content::TopControlsState current, bool animate) { render_view()->UpdateTopControlsState(constraints, current, animate); } void ChromeRenderViewObserver::OnRetrieveWebappInformation( const GURL& expected_url) { WebFrame* main_frame = render_view()->GetWebView()->mainFrame(); WebDocument document = main_frame ? main_frame->document() : WebDocument(); WebElement head = document.isNull() ? WebElement() : document.head(); GURL document_url = document.isNull() ? GURL() : GURL(document.url()); // Make sure we're checking the right page. bool success = document_url == expected_url; bool is_mobile_webapp_capable = false; bool is_apple_mobile_webapp_capable = false; // Search the DOM for the webapp tags. if (!head.isNull()) { WebNodeList children = head.childNodes(); for (unsigned i = 0; i < children.length(); ++i) { WebNode child = children.item(i); if (!child.isElementNode()) continue; WebElement elem = child.to(); if (elem.hasTagName("meta") && elem.hasAttribute("name")) { std::string name = elem.getAttribute("name").utf8(); WebString content = elem.getAttribute("content"); if (LowerCaseEqualsASCII(content, "yes")) { if (name == "mobile-web-app-capable") { is_mobile_webapp_capable = true; } else if (name == "apple-mobile-web-app-capable") { is_apple_mobile_webapp_capable = true; } } } } } else { success = false; } bool is_only_apple_mobile_webapp_capable = is_apple_mobile_webapp_capable && !is_mobile_webapp_capable; if (main_frame && is_only_apple_mobile_webapp_capable) { WebKit::WebConsoleMessage message( WebKit::WebConsoleMessage::LevelWarning, " is " "deprecated. Please include - " "http://developers.google.com/chrome/mobile/docs/installtohomescreen"); main_frame->addMessageToConsole(message); } Send(new ChromeViewHostMsg_DidRetrieveWebappInformation( routing_id(), success, is_mobile_webapp_capable, is_apple_mobile_webapp_capable, expected_url)); } #endif void ChromeRenderViewObserver::OnSetWindowFeatures( const WebWindowFeatures& window_features) { render_view()->GetWebView()->setWindowFeatures(window_features); } void ChromeRenderViewObserver::Navigate(const GURL& url) { // Execute cache clear operations that were postponed until a navigation // event (including tab reload). if (chrome_render_process_observer_) chrome_render_process_observer_->ExecutePendingClearCache(); } void ChromeRenderViewObserver::OnSetClientSidePhishingDetection( bool enable_phishing_detection) { #if defined(FULL_SAFE_BROWSING) && !defined(OS_CHROMEOS) phishing_classifier_ = enable_phishing_detection ? safe_browsing::PhishingClassifierDelegate::Create( render_view(), NULL) : NULL; #endif } void ChromeRenderViewObserver::OnSetVisuallyDeemphasized(bool deemphasized) { bool already_deemphasized = !!dimmed_color_overlay_.get(); if (already_deemphasized == deemphasized) return; if (deemphasized) { // 70% opaque grey. SkColor greyish = SkColorSetARGB(178, 0, 0, 0); dimmed_color_overlay_.reset( new WebViewColorOverlay(render_view(), greyish)); } else { dimmed_color_overlay_.reset(); } } void ChromeRenderViewObserver::OnRequestThumbnailForContextNode( int thumbnail_min_area_pixels, gfx::Size thumbnail_max_size_pixels) { WebNode context_node = render_view()->GetContextMenuNode(); SkBitmap thumbnail; gfx::Size original_size; if (!context_node.isNull() && context_node.isElementNode()) { WebKit::WebImage image = context_node.to().imageContents(); original_size = image.size(); thumbnail = Downscale(image, thumbnail_min_area_pixels, thumbnail_max_size_pixels); } Send(new ChromeViewHostMsg_RequestThumbnailForContextNode_ACK( routing_id(), thumbnail, original_size)); } void ChromeRenderViewObserver::OnGetFPS() { float fps = (render_view()->GetFilteredTimePerFrame() > 0.0f)? 1.0f / render_view()->GetFilteredTimePerFrame() : 0.0f; Send(new ChromeViewHostMsg_FPS(routing_id(), fps)); } bool ChromeRenderViewObserver::allowDatabase( WebFrame* frame, const WebString& name, const WebString& display_name, unsigned long estimated_size) { return content_settings_->AllowDatabase( frame, name, display_name, estimated_size); } bool ChromeRenderViewObserver::allowFileSystem(WebFrame* frame) { return content_settings_->AllowFileSystem(frame); } bool ChromeRenderViewObserver::allowImage(WebFrame* frame, bool enabled_per_settings, const WebURL& image_url) { return content_settings_->AllowImage(frame, enabled_per_settings, image_url); } bool ChromeRenderViewObserver::allowIndexedDB(WebFrame* frame, const WebString& name, const WebSecurityOrigin& origin) { return content_settings_->AllowIndexedDB(frame, name, origin); } bool ChromeRenderViewObserver::allowPlugins(WebFrame* frame, bool enabled_per_settings) { return content_settings_->AllowPlugins(frame, enabled_per_settings); } bool ChromeRenderViewObserver::allowScript(WebFrame* frame, bool enabled_per_settings) { return content_settings_->AllowScript(frame, enabled_per_settings); } bool ChromeRenderViewObserver::allowScriptFromSource( WebFrame* frame, bool enabled_per_settings, const WebURL& script_url) { return content_settings_->AllowScriptFromSource(frame, enabled_per_settings, script_url); } bool ChromeRenderViewObserver::allowStorage(WebFrame* frame, bool local) { return content_settings_->AllowStorage(frame, local); } bool ChromeRenderViewObserver::allowReadFromClipboard(WebFrame* frame, bool default_value) { bool allowed = false; // TODO(dcheng): Should we consider a toURL() method on WebSecurityOrigin? Send(new ChromeViewHostMsg_CanTriggerClipboardRead( routing_id(), GURL(frame->document().securityOrigin().toString().utf8()), &allowed)); return allowed; } bool ChromeRenderViewObserver::allowWriteToClipboard(WebFrame* frame, bool default_value) { bool allowed = false; Send(new ChromeViewHostMsg_CanTriggerClipboardWrite( routing_id(), GURL(frame->document().securityOrigin().toString().utf8()), &allowed)); return allowed; } bool ChromeRenderViewObserver::allowWebComponents(const WebDocument& document, bool defaultValue) { if (defaultValue) return true; WebSecurityOrigin origin = document.securityOrigin(); if (EqualsASCII(origin.protocol(), chrome::kChromeUIScheme)) return true; if (const extensions::Extension* extension = GetExtension(origin)) { if (extension->HasAPIPermission(APIPermission::kExperimental)) return true; } return false; } bool ChromeRenderViewObserver::allowHTMLNotifications( const WebDocument& document) { CommandLine* command_line = CommandLine::ForCurrentProcess(); if (command_line->HasSwitch(switches::kDisableHTMLNotifications)) return false; WebSecurityOrigin origin = document.securityOrigin(); const extensions::Extension* extension = GetExtension(origin); return extension && extension->HasAPIPermission(APIPermission::kNotification); } bool ChromeRenderViewObserver::allowMutationEvents(const WebDocument& document, bool default_value) { WebSecurityOrigin origin = document.securityOrigin(); const extensions::Extension* extension = GetExtension(origin); if (extension && extension->is_platform_app()) return false; return default_value; } bool ChromeRenderViewObserver::allowPushState(const WebDocument& document) { WebSecurityOrigin origin = document.securityOrigin(); const extensions::Extension* extension = GetExtension(origin); return !extension || !extension->is_platform_app(); } static void SendInsecureContentSignal(int signal) { UMA_HISTOGRAM_ENUMERATION("SSL.InsecureContent", signal, INSECURE_CONTENT_NUM_EVENTS); } bool ChromeRenderViewObserver::allowDisplayingInsecureContent( WebKit::WebFrame* frame, bool allowed_per_settings, const WebKit::WebSecurityOrigin& origin, const WebKit::WebURL& resource_url) { SendInsecureContentSignal(INSECURE_CONTENT_DISPLAY); std::string origin_host(origin.host().utf8()); GURL frame_gurl(frame->document().url()); if (isHostInDomain(origin_host, kGoogleDotCom)) { SendInsecureContentSignal(INSECURE_CONTENT_DISPLAY_HOST_GOOGLE); if (StartsWithASCII(frame_gurl.path(), kGoogleSupportPathPrefix, false)) { SendInsecureContentSignal(INSECURE_CONTENT_DISPLAY_HOST_GOOGLE_SUPPORT); } else if (StartsWithASCII(frame_gurl.path(), kGoogleIntlPathPrefix, false)) { SendInsecureContentSignal(INSECURE_CONTENT_DISPLAY_HOST_GOOGLE_INTL); } } if (origin_host == kWWWDotGoogleDotCom) { SendInsecureContentSignal(INSECURE_CONTENT_DISPLAY_HOST_WWW_GOOGLE); if (StartsWithASCII(frame_gurl.path(), kGoogleReaderPathPrefix, false)) SendInsecureContentSignal(INSECURE_CONTENT_DISPLAY_HOST_GOOGLE_READER); } else if (origin_host == kMailDotGoogleDotCom) { SendInsecureContentSignal(INSECURE_CONTENT_DISPLAY_HOST_MAIL_GOOGLE); } else if (origin_host == kPlusDotGoogleDotCom) { SendInsecureContentSignal(INSECURE_CONTENT_DISPLAY_HOST_PLUS_GOOGLE); } else if (origin_host == kDocsDotGoogleDotCom) { SendInsecureContentSignal(INSECURE_CONTENT_DISPLAY_HOST_DOCS_GOOGLE); } else if (origin_host == kSitesDotGoogleDotCom) { SendInsecureContentSignal(INSECURE_CONTENT_DISPLAY_HOST_SITES_GOOGLE); } else if (origin_host == kPicasawebDotGoogleDotCom) { SendInsecureContentSignal(INSECURE_CONTENT_DISPLAY_HOST_PICASAWEB_GOOGLE); } else if (origin_host == kCodeDotGoogleDotCom) { SendInsecureContentSignal(INSECURE_CONTENT_DISPLAY_HOST_CODE_GOOGLE); } else if (origin_host == kGroupsDotGoogleDotCom) { SendInsecureContentSignal(INSECURE_CONTENT_DISPLAY_HOST_GROUPS_GOOGLE); } else if (origin_host == kMapsDotGoogleDotCom) { SendInsecureContentSignal(INSECURE_CONTENT_DISPLAY_HOST_MAPS_GOOGLE); } else if (origin_host == kWWWDotYoutubeDotCom) { SendInsecureContentSignal(INSECURE_CONTENT_DISPLAY_HOST_YOUTUBE); } GURL resource_gurl(resource_url); if (EndsWith(resource_gurl.path(), kDotHTML, false)) SendInsecureContentSignal(INSECURE_CONTENT_DISPLAY_HTML); if (allowed_per_settings || allow_displaying_insecure_content_) return true; Send(new ChromeViewHostMsg_DidBlockDisplayingInsecureContent(routing_id())); return false; } bool ChromeRenderViewObserver::allowRunningInsecureContent( WebKit::WebFrame* frame, bool allowed_per_settings, const WebKit::WebSecurityOrigin& origin, const WebKit::WebURL& resource_url) { std::string origin_host(origin.host().utf8()); GURL frame_gurl(frame->document().url()); DCHECK_EQ(frame_gurl.host(), origin_host); bool is_google = isHostInDomain(origin_host, kGoogleDotCom); if (is_google) { SendInsecureContentSignal(INSECURE_CONTENT_RUN_HOST_GOOGLE); if (StartsWithASCII(frame_gurl.path(), kGoogleSupportPathPrefix, false)) { SendInsecureContentSignal(INSECURE_CONTENT_RUN_HOST_GOOGLE_SUPPORT); } else if (StartsWithASCII(frame_gurl.path(), kGoogleIntlPathPrefix, false)) { SendInsecureContentSignal(INSECURE_CONTENT_RUN_HOST_GOOGLE_INTL); } } if (origin_host == kWWWDotGoogleDotCom) { SendInsecureContentSignal(INSECURE_CONTENT_RUN_HOST_WWW_GOOGLE); if (StartsWithASCII(frame_gurl.path(), kGoogleReaderPathPrefix, false)) SendInsecureContentSignal(INSECURE_CONTENT_RUN_HOST_GOOGLE_READER); } else if (origin_host == kMailDotGoogleDotCom) { SendInsecureContentSignal(INSECURE_CONTENT_RUN_HOST_MAIL_GOOGLE); } else if (origin_host == kPlusDotGoogleDotCom) { SendInsecureContentSignal(INSECURE_CONTENT_RUN_HOST_PLUS_GOOGLE); } else if (origin_host == kDocsDotGoogleDotCom) { SendInsecureContentSignal(INSECURE_CONTENT_RUN_HOST_DOCS_GOOGLE); } else if (origin_host == kSitesDotGoogleDotCom) { SendInsecureContentSignal(INSECURE_CONTENT_RUN_HOST_SITES_GOOGLE); } else if (origin_host == kPicasawebDotGoogleDotCom) { SendInsecureContentSignal(INSECURE_CONTENT_RUN_HOST_PICASAWEB_GOOGLE); } else if (origin_host == kCodeDotGoogleDotCom) { SendInsecureContentSignal(INSECURE_CONTENT_RUN_HOST_CODE_GOOGLE); } else if (origin_host == kGroupsDotGoogleDotCom) { SendInsecureContentSignal(INSECURE_CONTENT_RUN_HOST_GROUPS_GOOGLE); } else if (origin_host == kMapsDotGoogleDotCom) { SendInsecureContentSignal(INSECURE_CONTENT_RUN_HOST_MAPS_GOOGLE); } else if (origin_host == kWWWDotYoutubeDotCom) { SendInsecureContentSignal(INSECURE_CONTENT_RUN_HOST_YOUTUBE); } else if (EndsWith(origin_host, kDotGoogleUserContentDotCom, false)) { SendInsecureContentSignal(INSECURE_CONTENT_RUN_HOST_GOOGLEUSERCONTENT); } GURL resource_gurl(resource_url); if (resource_gurl.host() == kWWWDotYoutubeDotCom) SendInsecureContentSignal(INSECURE_CONTENT_RUN_TARGET_YOUTUBE); if (EndsWith(resource_gurl.path(), kDotJS, false)) SendInsecureContentSignal(INSECURE_CONTENT_RUN_JS); else if (EndsWith(resource_gurl.path(), kDotCSS, false)) SendInsecureContentSignal(INSECURE_CONTENT_RUN_CSS); else if (EndsWith(resource_gurl.path(), kDotSWF, false)) SendInsecureContentSignal(INSECURE_CONTENT_RUN_SWF); if (!allow_running_insecure_content_ && !allowed_per_settings) { content_settings_->DidNotAllowMixedScript(); return false; } return true; } bool ChromeRenderViewObserver::allowWebGLDebugRendererInfo(WebFrame* frame) { bool allowed = false; Send(new ChromeViewHostMsg_IsWebGLDebugRendererInfoAllowed( routing_id(), GURL(frame->top()->document().securityOrigin().toString().utf8()), &allowed)); return allowed; } void ChromeRenderViewObserver::didNotAllowPlugins(WebFrame* frame) { content_settings_->DidNotAllowPlugins(); } void ChromeRenderViewObserver::didNotAllowScript(WebFrame* frame) { content_settings_->DidNotAllowScript(); } void ChromeRenderViewObserver::OnSetIsPrerendering(bool is_prerendering) { if (is_prerendering) { DCHECK(!prerender::PrerenderHelper::Get(render_view())); // The PrerenderHelper will destroy itself either after recording histograms // or on destruction of the RenderView. new prerender::PrerenderHelper(render_view()); } } void ChromeRenderViewObserver::DidStartLoading() { if ((render_view()->GetEnabledBindings() & content::BINDINGS_POLICY_WEB_UI) && webui_javascript_.get()) { render_view()->EvaluateScript(webui_javascript_->frame_xpath, webui_javascript_->jscript, webui_javascript_->id, webui_javascript_->notify_result); webui_javascript_.reset(); } } void ChromeRenderViewObserver::DidStopLoading() { WebFrame* main_frame = render_view()->GetWebView()->mainFrame(); GURL osd_url = main_frame->document().openSearchDescriptionURL(); if (!osd_url.is_empty()) { Send(new ChromeViewHostMsg_PageHasOSDD( routing_id(), render_view()->GetPageId(), osd_url, search_provider::AUTODETECTED_PROVIDER)); } // Don't capture pages including refresh meta tag. if (HasRefreshMetaTag(main_frame)) return; CapturePageInfoLater( render_view()->GetPageId(), false, // preliminary_capture base::TimeDelta::FromMilliseconds( render_view()->GetContentStateImmediately() ? 0 : kDelayForCaptureMs)); } void ChromeRenderViewObserver::DidCommitProvisionalLoad( WebFrame* frame, bool is_new_navigation) { // Don't capture pages being not new, or including refresh meta tag. if (!is_new_navigation || HasRefreshMetaTag(frame)) return; CapturePageInfoLater( render_view()->GetPageId(), true, // preliminary_capture base::TimeDelta::FromMilliseconds(kDelayForForcedCaptureMs)); } void ChromeRenderViewObserver::DidClearWindowObject(WebFrame* frame) { if (render_view()->GetEnabledBindings() & content::BINDINGS_POLICY_EXTERNAL_HOST) { GetExternalHostBindings()->BindToJavascript(frame, "externalHost"); } } void ChromeRenderViewObserver::DidHandleGestureEvent( const WebGestureEvent& event) { if (event.type != WebKit::WebGestureEvent::GestureTap) return; WebKit::WebTextInputType text_input_type = render_view()->GetWebView()->textInputInfo().type; render_view()->Send(new ChromeViewHostMsg_FocusedNodeTouched( routing_id(), text_input_type != WebKit::WebTextInputTypeNone)); } void ChromeRenderViewObserver::DetailedConsoleMessageAdded( const base::string16& message, const base::string16& source, const base::string16& stack_trace_string, int32 line_number, int32 severity_level) { string16 trimmed_message = message; extensions::StackTrace stack_trace = GetStackTraceFromMessage( &trimmed_message, source, stack_trace_string, line_number); Send(new ChromeViewHostMsg_DetailedConsoleMessageAdded(routing_id(), trimmed_message, source, stack_trace, severity_level)); } void ChromeRenderViewObserver::CapturePageInfoLater(int page_id, bool preliminary_capture, base::TimeDelta delay) { capture_timer_.Start( FROM_HERE, delay, base::Bind(&ChromeRenderViewObserver::CapturePageInfo, base::Unretained(this), page_id, preliminary_capture)); } void ChromeRenderViewObserver::CapturePageInfo(int page_id, bool preliminary_capture) { // If |page_id| is obsolete, we should stop indexing and capturing a page. if (render_view()->GetPageId() != page_id) return; if (!render_view()->GetWebView()) return; WebFrame* main_frame = render_view()->GetWebView()->mainFrame(); if (!main_frame) return; // Don't index/capture pages that are in view source mode. if (main_frame->isViewSourceModeEnabled()) return; // Don't index/capture pages that failed to load. This only checks the top // level frame so the thumbnail may contain a frame that failed to load. WebDataSource* ds = main_frame->dataSource(); if (ds && ds->hasUnreachableURL()) return; // Don't index/capture pages that are being prerendered. if (prerender::PrerenderHelper::IsPrerendering(render_view())) return; // Retrieve the frame's full text (up to kMaxIndexChars), and pass it to the // translate helper for language detection and possible translation. string16 contents; base::TimeTicks capture_begin_time = base::TimeTicks::Now(); CaptureText(main_frame, &contents); UMA_HISTOGRAM_TIMES(kTranslateCaptureText, base::TimeTicks::Now() - capture_begin_time); if (translate_helper_) translate_helper_->PageCaptured(page_id, contents); // Skip indexing if this is not a new load. Note that the case where // page_id == last_indexed_page_id_ is more complicated, since we need to // reindex if the toplevel URL has changed (such as from a redirect), even // though this may not cause the page id to be incremented. if (page_id < last_indexed_page_id_) return; bool same_page_id = last_indexed_page_id_ == page_id; if (!preliminary_capture) last_indexed_page_id_ = page_id; // Get the URL for this page. GURL url(main_frame->document().url()); if (url.is_empty()) { if (!preliminary_capture) last_indexed_url_ = GURL(); return; } // If the page id is unchanged, check whether the URL (ignoring fragments) // has changed. If so, we need to reindex. Otherwise, assume this is a // reload, in-page navigation, or some other load type where we don't want to // reindex. Note: subframe navigations after onload increment the page id, // so these will trigger a reindex. GURL stripped_url(StripRef(url)); if (same_page_id && stripped_url == last_indexed_url_) return; if (!preliminary_capture) last_indexed_url_ = stripped_url; TRACE_EVENT0("renderer", "ChromeRenderViewObserver::CapturePageInfo"); if (contents.size()) { // Send the text to the browser for indexing (the browser might decide not // to index, if the URL is HTTPS for instance). Send(new ChromeViewHostMsg_PageContents(routing_id(), url, contents)); } #if defined(FULL_SAFE_BROWSING) // Will swap out the string. if (phishing_classifier_) phishing_classifier_->PageCaptured(&contents, preliminary_capture); #endif } void ChromeRenderViewObserver::CaptureText(WebFrame* frame, string16* contents) { contents->clear(); if (!frame) return; #ifdef TIME_TEXT_RETRIEVAL double begin = time_util::GetHighResolutionTimeNow(); #endif // get the contents of the frame *contents = frame->contentAsText(kMaxIndexChars); #ifdef TIME_TEXT_RETRIEVAL double end = time_util::GetHighResolutionTimeNow(); char buf[128]; sprintf_s(buf, "%d chars retrieved for indexing in %gms\n", contents.size(), (end - begin)*1000); OutputDebugStringA(buf); #endif // When the contents are clipped to the maximum, we don't want to have a // partial word indexed at the end that might have been clipped. Therefore, // terminate the string at the last space to ensure no words are clipped. if (contents->size() == kMaxIndexChars) { size_t last_space_index = contents->find_last_of(kWhitespaceUTF16); if (last_space_index == std::wstring::npos) return; // don't index if we got a huge block of text with no spaces contents->resize(last_space_index); } } ExternalHostBindings* ChromeRenderViewObserver::GetExternalHostBindings() { if (!external_host_bindings_.get()) { external_host_bindings_.reset(new ExternalHostBindings( render_view(), routing_id())); } return external_host_bindings_.get(); } bool ChromeRenderViewObserver::IsStrictSecurityHost(const std::string& host) { return (strict_security_hosts_.find(host) != strict_security_hosts_.end()); } const extensions::Extension* ChromeRenderViewObserver::GetExtension( const WebSecurityOrigin& origin) const { if (!EqualsASCII(origin.protocol(), extensions::kExtensionScheme)) return NULL; const std::string extension_id = origin.host().utf8().data(); if (!extension_dispatcher_->IsExtensionActive(extension_id)) return NULL; return extension_dispatcher_->extensions()->GetByID(extension_id); } bool ChromeRenderViewObserver::HasRefreshMetaTag(WebFrame* frame) { if (!frame) return false; WebElement head = frame->document().head(); if (head.isNull() || !head.hasChildNodes()) return false; const WebString tag_name(ASCIIToUTF16("meta")); const WebString attribute_name(ASCIIToUTF16("http-equiv")); WebNodeList children = head.childNodes(); for (size_t i = 0; i < children.length(); ++i) { WebNode node = children.item(i); if (!node.isElementNode()) continue; WebElement element = node.to(); if (!element.hasTagName(tag_name)) continue; WebString value = element.getAttribute(attribute_name); if (value.isNull() || !LowerCaseEqualsASCII(value, "refresh")) continue; return true; } return false; }