diff options
Diffstat (limited to 'chromium/chrome/renderer/chrome_render_frame_observer.cc')
-rw-r--r-- | chromium/chrome/renderer/chrome_render_frame_observer.cc | 515 |
1 files changed, 515 insertions, 0 deletions
diff --git a/chromium/chrome/renderer/chrome_render_frame_observer.cc b/chromium/chrome/renderer/chrome_render_frame_observer.cc new file mode 100644 index 00000000000..fa001df3912 --- /dev/null +++ b/chromium/chrome/renderer/chrome_render_frame_observer.cc @@ -0,0 +1,515 @@ +// Copyright 2013 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_frame_observer.h" + +#include <stddef.h> +#include <string.h> + +#include <limits> +#include <string> +#include <utility> +#include <vector> + +#include "base/bind.h" +#include "base/command_line.h" +#include "base/metrics/histogram_macros.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/utf_string_conversions.h" +#include "build/build_config.h" +#include "chrome/common/chrome_constants.h" +#include "chrome/common/chrome_isolated_world_ids.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/common/open_search_description_document_handler.mojom.h" +#include "chrome/common/prerender_messages.h" +#include "chrome/common/render_messages.h" +#include "chrome/renderer/prerender/prerender_helper.h" +#include "chrome/renderer/web_apps.h" +#include "components/crash/core/common/crash_key.h" +#include "components/offline_pages/buildflags/buildflags.h" +#include "components/safe_browsing/buildflags.h" +#include "components/translate/content/renderer/translate_helper.h" +#include "components/web_cache/renderer/web_cache_impl.h" +#include "content/public/common/bindings_policy.h" +#include "content/public/renderer/render_frame.h" +#include "content/public/renderer/render_view.h" +#include "content/public/renderer/window_features_converter.h" +#include "extensions/common/constants.h" +#include "printing/buildflags/buildflags.h" +#include "services/service_manager/public/cpp/binder_registry.h" +#include "skia/ext/image_operations.h" +#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h" +#include "third_party/blink/public/common/associated_interfaces/associated_interface_registry.h" +#include "third_party/blink/public/platform/web_image.h" +#include "third_party/blink/public/platform/web_url_request.h" +#include "third_party/blink/public/web/web_console_message.h" +#include "third_party/blink/public/web/web_document.h" +#include "third_party/blink/public/web/web_document_loader.h" +#include "third_party/blink/public/web/web_element.h" +#include "third_party/blink/public/web/web_frame_content_dumper.h" +#include "third_party/blink/public/web/web_local_frame.h" +#include "third_party/blink/public/web/web_node.h" +#include "third_party/blink/public/web/web_security_policy.h" +#include "third_party/blink/public/web/web_view.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "ui/gfx/codec/jpeg_codec.h" +#include "ui/gfx/codec/png_codec.h" +#include "ui/gfx/geometry/size_f.h" +#include "url/gurl.h" + +#if !defined(OS_ANDROID) +#include "chrome/renderer/searchbox/searchbox_extension.h" +#endif // !defined(OS_ANDROID) + +#if BUILDFLAG(FULL_SAFE_BROWSING) +#include "chrome/renderer/safe_browsing/phishing_classifier_delegate.h" +#endif + +#if BUILDFLAG(ENABLE_OFFLINE_PAGES) +#include "chrome/common/mhtml_page_notifier.mojom.h" +#endif + +#if BUILDFLAG(ENABLE_PRINTING) +#include "components/printing/common/print_messages.h" +#include "components/printing/renderer/print_render_frame_helper.h" +#endif + +using blink::WebDocumentLoader; +using blink::WebElement; +using blink::WebFrameContentDumper; +using blink::WebLocalFrame; +using blink::WebNode; +using blink::WebString; +using content::RenderFrame; + +// 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 kTranslateCaptureText[] = "Translate.CaptureText"; + +// For a page that auto-refreshes, we still show the bubble, if +// the refresh delay is less than this value (in seconds). +static constexpr base::TimeDelta kLocationChangeInterval = + base::TimeDelta::FromSeconds(10); + +// For the context menu, we want to keep transparency as is instead of +// replacing transparent pixels with black ones +static const bool kDiscardTransparencyForContextMenu = false; + +namespace { + +// 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(const SkBitmap& image, + int thumbnail_min_area_pixels, + const gfx::Size& thumbnail_max_size_pixels) { + if (image.isNull()) + return SkBitmap(); + + gfx::Size image_size(image.width(), image.height()); + + if (image_size.GetArea() < thumbnail_min_area_pixels) + return image; + + if (image_size.width() <= thumbnail_max_size_pixels.width() && + image_size.height() <= thumbnail_max_size_pixels.height()) + return image; + + gfx::SizeF scaled_size = gfx::SizeF(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, + skia::ImageOperations::RESIZE_GOOD, + static_cast<int>(scaled_size.width()), + static_cast<int>(scaled_size.height())); +} + +} // namespace + +ChromeRenderFrameObserver::ChromeRenderFrameObserver( + content::RenderFrame* render_frame, + web_cache::WebCacheImpl* web_cache_impl) + : content::RenderFrameObserver(render_frame), + translate_helper_(nullptr), + phishing_classifier_(nullptr), + web_cache_impl_(web_cache_impl) { + render_frame->GetAssociatedInterfaceRegistry()->AddInterface( + base::Bind(&ChromeRenderFrameObserver::OnRenderFrameObserverRequest, + base::Unretained(this))); + // Don't do anything else for subframes. + if (!render_frame->IsMainFrame()) + return; + +#if BUILDFLAG(SAFE_BROWSING_CSD) + const base::CommandLine& command_line = + *base::CommandLine::ForCurrentProcess(); + if (!command_line.HasSwitch(switches::kDisableClientSidePhishingDetection)) + SetClientSidePhishingDetection(true); +#endif + translate_helper_ = new translate::TranslateHelper( + render_frame, ISOLATED_WORLD_ID_TRANSLATE, extensions::kExtensionScheme); +} + +ChromeRenderFrameObserver::~ChromeRenderFrameObserver() { +} + +void ChromeRenderFrameObserver::OnInterfaceRequestForFrame( + const std::string& interface_name, + mojo::ScopedMessagePipeHandle* interface_pipe) { + registry_.TryBindInterface(interface_name, interface_pipe); +} + +bool ChromeRenderFrameObserver::OnAssociatedInterfaceRequestForFrame( + const std::string& interface_name, + mojo::ScopedInterfaceEndpointHandle* handle) { + return associated_interfaces_.TryBindInterface(interface_name, handle); +} + +bool ChromeRenderFrameObserver::OnMessageReceived(const IPC::Message& message) { + // Filter only. + bool handled = true; + IPC_BEGIN_MESSAGE_MAP(ChromeRenderFrameObserver, message) + IPC_MESSAGE_HANDLER(PrerenderMsg_SetIsPrerendering, OnSetIsPrerendering) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + if (handled) + return false; + + IPC_BEGIN_MESSAGE_MAP(ChromeRenderFrameObserver, message) +#if BUILDFLAG(ENABLE_PRINTING) + IPC_MESSAGE_HANDLER(PrintMsg_PrintNodeUnderContextMenu, + OnPrintNodeUnderContextMenu) +#endif + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + + return handled; +} + +void ChromeRenderFrameObserver::OnSetIsPrerendering( + prerender::PrerenderMode mode, + const std::string& histogram_prefix) { + if (mode != prerender::NO_PRERENDER) { + // If the PrerenderHelper for this frame already exists, don't create it. It + // can already be created for subframes during handling of + // RenderFrameCreated, if the parent frame was prerendering at time of + // subframe creation. + if (prerender::PrerenderHelper::Get(render_frame())) + return; + + // The PrerenderHelper will destroy itself either after recording histograms + // or on destruction of the RenderView. + new prerender::PrerenderHelper(render_frame(), mode, histogram_prefix); + } +} + +void ChromeRenderFrameObserver::RequestReloadImageForContextNode() { + WebLocalFrame* frame = render_frame()->GetWebFrame(); + // TODO(dglazkov): This code is clearly in the wrong place. Need + // to investigate what it is doing and fix (http://crbug.com/606164). + WebNode context_node = frame->ContextMenuNode(); + if (!context_node.IsNull()) { + frame->ReloadImage(context_node); + } +} + +void ChromeRenderFrameObserver::RequestThumbnailForContextNode( + int32_t thumbnail_min_area_pixels, + const gfx::Size& thumbnail_max_size_pixels, + chrome::mojom::ImageFormat image_format, + RequestThumbnailForContextNodeCallback callback) { + WebNode context_node = render_frame()->GetWebFrame()->ContextMenuNode(); + SkBitmap thumbnail; + gfx::Size original_size; + if (!context_node.IsNull() && context_node.IsElementNode()) { + SkBitmap image = context_node.To<WebElement>().ImageContents(); + original_size = gfx::Size(image.width(), image.height()); + thumbnail = Downscale(image, + thumbnail_min_area_pixels, + thumbnail_max_size_pixels); + } + + SkBitmap bitmap; + if (thumbnail.colorType() == kN32_SkColorType) { + bitmap = thumbnail; + } else { + SkImageInfo info = thumbnail.info().makeColorType(kN32_SkColorType); + if (bitmap.tryAllocPixels(info)) { + thumbnail.readPixels(info, bitmap.getPixels(), bitmap.rowBytes(), 0, 0); + } + } + + std::vector<uint8_t> thumbnail_data; + constexpr int kDefaultQuality = 90; + std::vector<unsigned char> data; + + switch (image_format) { + case chrome::mojom::ImageFormat::PNG: + if (gfx::PNGCodec::EncodeBGRASkBitmap( + bitmap, kDiscardTransparencyForContextMenu, &data)) { + thumbnail_data.swap(data); + } + break; + case chrome::mojom::ImageFormat::JPEG: + if (gfx::JPEGCodec::Encode(bitmap, kDefaultQuality, &data)) + thumbnail_data.swap(data); + break; + } + std::move(callback).Run(thumbnail_data, original_size); +} + +void ChromeRenderFrameObserver::OnPrintNodeUnderContextMenu() { +#if BUILDFLAG(ENABLE_PRINTING) + printing::PrintRenderFrameHelper* helper = + printing::PrintRenderFrameHelper::Get(render_frame()); + if (helper) + helper->PrintNode(render_frame()->GetWebFrame()->ContextMenuNode()); +#endif +} + +void ChromeRenderFrameObserver::GetWebApplicationInfo( + GetWebApplicationInfoCallback callback) { + WebLocalFrame* frame = render_frame()->GetWebFrame(); + + WebApplicationInfo web_app_info; + web_apps::ParseWebAppFromWebDocument(frame, &web_app_info); + + // The warning below is specific to mobile but it doesn't hurt to show it even + // if the Chromium build is running on a desktop. It will get more exposition. + if (web_app_info.mobile_capable == WebApplicationInfo::MOBILE_CAPABLE_APPLE) { + blink::WebConsoleMessage message( + blink::mojom::ConsoleMessageLevel::kWarning, + "<meta name=\"apple-mobile-web-app-capable\" content=\"yes\"> is " + "deprecated. Please include <meta name=\"mobile-web-app-capable\" " + "content=\"yes\">"); + frame->AddMessageToConsole(message); + } + + // Prune out any data URLs in the set of icons. The browser process expects + // any icon with a data URL to have originated from a favicon. We don't want + // to decode arbitrary data URLs in the browser process. See + // http://b/issue?id=1162972 + for (auto it = web_app_info.icons.begin(); it != web_app_info.icons.end();) { + if (it->url.SchemeIs(url::kDataScheme)) + it = web_app_info.icons.erase(it); + else + ++it; + } + + // Truncate the strings we send to the browser process. + web_app_info.title = + web_app_info.title.substr(0, chrome::kMaxMetaTagAttributeLength); + web_app_info.description = + web_app_info.description.substr(0, chrome::kMaxMetaTagAttributeLength); + + std::move(callback).Run(web_app_info); +} + +void ChromeRenderFrameObserver::SetClientSidePhishingDetection( + bool enable_phishing_detection) { +#if BUILDFLAG(SAFE_BROWSING_CSD) + phishing_classifier_ = + enable_phishing_detection + ? safe_browsing::PhishingClassifierDelegate::Create(render_frame(), + nullptr) + : nullptr; +#endif +} + +void ChromeRenderFrameObserver::ExecuteWebUIJavaScript( + const base::string16& javascript) { +#if !defined(OS_ANDROID) + webui_javascript_.push_back(javascript); +#endif +} + +void ChromeRenderFrameObserver::DidFinishLoad() { + WebLocalFrame* frame = render_frame()->GetWebFrame(); + // Don't do anything for subframes. + if (frame->Parent()) + return; + + GURL osdd_url = frame->GetDocument().OpenSearchDescriptionURL(); + if (!osdd_url.is_empty()) { + mojo::AssociatedRemote<chrome::mojom::OpenSearchDescriptionDocumentHandler> + osdd_handler; + render_frame()->GetRemoteAssociatedInterfaces()->GetInterface( + &osdd_handler); + osdd_handler->PageHasOpenSearchDescriptionDocument( + frame->GetDocument().Url(), osdd_url); + } +} + +void ChromeRenderFrameObserver::DidCreateNewDocument() { +#if BUILDFLAG(ENABLE_OFFLINE_PAGES) + DCHECK(render_frame()); + if (!render_frame()->IsMainFrame()) + return; + + DCHECK(render_frame()->GetWebFrame()); + blink::WebDocumentLoader* doc_loader = + render_frame()->GetWebFrame()->GetDocumentLoader(); + DCHECK(doc_loader); + + if (!doc_loader->HasBeenLoadedAsWebArchive()) + return; + + // Connect to Mojo service on browser to notify it of the page's archive + // properties. + mojo::AssociatedRemote<offline_pages::mojom::MhtmlPageNotifier> + mhtml_notifier; + render_frame()->GetRemoteAssociatedInterfaces()->GetInterface( + &mhtml_notifier); + DCHECK(mhtml_notifier); + blink::WebArchiveInfo info = doc_loader->GetArchiveInfo(); + + mhtml_notifier->NotifyMhtmlPageLoadAttempted(info.load_result, info.url, + info.date); +#endif +} + +void ChromeRenderFrameObserver::ReadyToCommitNavigation( + WebDocumentLoader* document_loader) { + // Execute cache clear operations that were postponed until a navigation + // event (including tab reload). + if (render_frame()->IsMainFrame() && web_cache_impl_) + web_cache_impl_->ExecutePendingClearCache(); + + // Let translate_helper do any preparatory work for loading a URL. + if (!translate_helper_) + return; + + translate_helper_->PrepareForUrl( + render_frame()->GetWebFrame()->GetDocument().Url()); +} + +void ChromeRenderFrameObserver::DidCommitProvisionalLoad( + bool is_same_document_navigation, + ui::PageTransition transition) { + WebLocalFrame* frame = render_frame()->GetWebFrame(); + + // Don't do anything for subframes. + if (frame->Parent()) + return; + + static crash_reporter::CrashKeyString<8> view_count_key("view-count"); + view_count_key.Set( + base::NumberToString(content::RenderView::GetRenderViewCount())); + +#if !defined(OS_ANDROID) + if (render_frame()->GetEnabledBindings() & + content::kWebUIBindingsPolicyMask) { + for (const auto& script : webui_javascript_) + render_frame()->ExecuteJavaScript(script); + webui_javascript_.clear(); + } +#endif +} + +void ChromeRenderFrameObserver::DidClearWindowObject() { +#if !defined(OS_ANDROID) + const base::CommandLine& command_line = + *base::CommandLine::ForCurrentProcess(); + if (command_line.HasSwitch(switches::kInstantProcess)) + SearchBoxExtension::Install(render_frame()->GetWebFrame()); +#endif // !defined(OS_ANDROID) +} + +void ChromeRenderFrameObserver::CapturePageText(TextCaptureType capture_type) { + WebLocalFrame* frame = render_frame()->GetWebFrame(); + if (!frame) + return; + + // Don't capture pages that have pending redirect or location change. + if (frame->IsNavigationScheduledWithin(kLocationChangeInterval)) + return; + + // Don't index/capture pages that are in view source mode. + if (frame->IsViewSourceModeEnabled()) + return; + + // Don't capture text of the error pages. + WebDocumentLoader* document_loader = frame->GetDocumentLoader(); + if (document_loader && document_loader->HasUnreachableURL()) + return; + + // Don't index/capture pages that are being prerendered. + if (prerender::PrerenderHelper::IsPrerendering(render_frame())) + return; + + base::TimeTicks capture_begin_time = base::TimeTicks::Now(); + + // Retrieve the frame's full text (up to kMaxIndexChars), and pass it to the + // translate helper for language detection and possible translation. + // TODO(dglazkov): WebFrameContentDumper should only be used for + // testing purposes. See http://crbug.com/585164. + base::string16 contents = + WebFrameContentDumper::DeprecatedDumpFrameTreeAsText(frame, + kMaxIndexChars) + .Utf16(); + + UMA_HISTOGRAM_TIMES(kTranslateCaptureText, + base::TimeTicks::Now() - capture_begin_time); + + // We should run language detection only once. Parsing finishes before + // the page loads, so let's pick that timing. + if (translate_helper_ && capture_type == PRELIMINARY_CAPTURE) { + translate_helper_->PageCaptured(contents); + } + + TRACE_EVENT0("renderer", "ChromeRenderFrameObserver::CapturePageText"); + +#if BUILDFLAG(SAFE_BROWSING_CSD) + // Will swap out the string. + if (phishing_classifier_) + phishing_classifier_->PageCaptured(&contents, + capture_type == PRELIMINARY_CAPTURE); +#endif +} + +void ChromeRenderFrameObserver::DidMeaningfulLayout( + blink::WebMeaningfulLayout layout_type) { + // Don't do any work for subframes. + if (!render_frame()->IsMainFrame()) + return; + + switch (layout_type) { + case blink::WebMeaningfulLayout::kFinishedParsing: + CapturePageText(PRELIMINARY_CAPTURE); + break; + case blink::WebMeaningfulLayout::kFinishedLoading: + CapturePageText(FINAL_CAPTURE); + break; + default: + break; + } +} + +void ChromeRenderFrameObserver::OnDestruct() { + delete this; +} + +void ChromeRenderFrameObserver::OnRenderFrameObserverRequest( + mojo::PendingAssociatedReceiver<chrome::mojom::ChromeRenderFrame> + receiver) { + receivers_.Add(this, std::move(receiver)); +} + +void ChromeRenderFrameObserver::SetWindowFeatures( + blink::mojom::WindowFeaturesPtr window_features) { + render_frame()->GetRenderView()->GetWebView()->SetWindowFeatures( + content::ConvertMojoWindowFeaturesToWebWindowFeatures(*window_features)); +} |