// 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_process_observer.h"
#include <limits>
#include <vector>
#include "base/allocator/allocator_extension.h"
#include "base/bind.h"
#include "base/command_line.h"
#include "base/file_util.h"
#include "base/memory/weak_ptr.h"
#include "base/message_loop/message_loop.h"
#include "base/metrics/field_trial.h"
#include "base/metrics/histogram.h"
#include "base/metrics/statistics_recorder.h"
#include "base/native_library.h"
#include "base/path_service.h"
#include "base/strings/utf_string_conversions.h"
#include "base/threading/platform_thread.h"
#include "chrome/common/child_process_logging.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/metrics/variations/variations_util.h"
#include "chrome/common/net/net_resource_provider.h"
#include "chrome/common/render_messages.h"
#include "chrome/common/url_constants.h"
#include "chrome/renderer/chrome_content_renderer_client.h"
#include "chrome/renderer/content_settings_observer.h"
#include "chrome/renderer/extensions/extension_localization_peer.h"
#include "chrome/renderer/security_filter_peer.h"
#include "content/public/child/resource_dispatcher_delegate.h"
#include "content/public/renderer/render_thread.h"
#include "content/public/renderer/render_view.h"
#include "content/public/renderer/render_view_visitor.h"
#include "crypto/nss_util.h"
#include "net/base/net_errors.h"
#include "net/base/net_module.h"
#include "third_party/WebKit/public/web/WebCache.h"
#include "third_party/WebKit/public/web/WebCrossOriginPreflightResultCache.h"
#include "third_party/WebKit/public/web/WebDocument.h"
#include "third_party/WebKit/public/web/WebFontCache.h"
#include "third_party/WebKit/public/web/WebFrame.h"
#include "third_party/WebKit/public/web/WebRuntimeFeatures.h"
#include "third_party/WebKit/public/web/WebSecurityPolicy.h"
#include "third_party/WebKit/public/web/WebView.h"
#include "third_party/sqlite/sqlite3.h"
#include "v8/include/v8.h"
#if defined(OS_WIN)
#include "base/win/iat_patch_function.h"
#endif
using blink::WebCache;
using blink::WebCrossOriginPreflightResultCache;
using blink::WebFontCache;
using blink::WebRuntimeFeatures;
using blink::WebSecurityPolicy;
using blink::WebString;
using content::RenderThread;
namespace {
const int kCacheStatsDelayMS = 2000;
const size_t kUnitializedCacheCapacity = UINT_MAX;
class RendererResourceDelegate : public content::ResourceDispatcherDelegate {
public:
RendererResourceDelegate()
: weak_factory_(this) {
}
virtual webkit_glue::ResourceLoaderBridge::Peer* OnRequestComplete(
webkit_glue::ResourceLoaderBridge::Peer* current_peer,
ResourceType::Type resource_type,
int error_code) OVERRIDE {
// Update the browser about our cache.
// Rate limit informing the host of our cache stats.
if (!weak_factory_.HasWeakPtrs()) {
base::MessageLoop::current()->PostDelayedTask(
FROM_HERE,
base::Bind(&RendererResourceDelegate::InformHostOfCacheStats,
weak_factory_.GetWeakPtr()),
base::TimeDelta::FromMilliseconds(kCacheStatsDelayMS));
}
if (error_code == net::ERR_ABORTED) {
return NULL;
}
// Resource canceled with a specific error are filtered.
return SecurityFilterPeer::CreateSecurityFilterPeerForDeniedRequest(
resource_type, current_peer, error_code);
}
virtual webkit_glue::ResourceLoaderBridge::Peer* OnReceivedResponse(
webkit_glue::ResourceLoaderBridge::Peer* current_peer,
const std::string& mime_type,
const GURL& url) OVERRIDE {
return ExtensionLocalizationPeer::CreateExtensionLocalizationPeer(
current_peer, RenderThread::Get(), mime_type, url);
}
private:
void InformHostOfCacheStats() {
WebCache::UsageStats stats;
WebCache::getUsageStats(&stats);
RenderThread::Get()->Send(new ChromeViewHostMsg_UpdatedCacheStats(stats));
}
base::WeakPtrFactory<RendererResourceDelegate> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(RendererResourceDelegate);
};
#if defined(OS_WIN)
static base::win::IATPatchFunction g_iat_patch_createdca;
HDC WINAPI CreateDCAPatch(LPCSTR driver_name,
LPCSTR device_name,
LPCSTR output,
const void* init_data) {
DCHECK(std::string("DISPLAY") == std::string(driver_name));
DCHECK(!device_name);
DCHECK(!output);
DCHECK(!init_data);
// CreateDC fails behind the sandbox, but not CreateCompatibleDC.
return CreateCompatibleDC(NULL);
}
static base::win::IATPatchFunction g_iat_patch_get_font_data;
DWORD WINAPI GetFontDataPatch(HDC hdc,
DWORD table,
DWORD offset,
LPVOID buffer,
DWORD length) {
int rv = GetFontData(hdc, table, offset, buffer, length);
if (rv == GDI_ERROR && hdc) {
HFONT font = static_cast<HFONT>(GetCurrentObject(hdc, OBJ_FONT));
LOGFONT logfont;
if (GetObject(font, sizeof(LOGFONT), &logfont)) {
std::vector<char> font_data;
RenderThread::Get()->PreCacheFont(logfont);
rv = GetFontData(hdc, table, offset, buffer, length);
RenderThread::Get()->ReleaseCachedFonts();
}
}
return rv;
}
#endif // OS_WIN
static const int kWaitForWorkersStatsTimeoutMS = 20;
class HeapStatisticsCollector {
public:
HeapStatisticsCollector() : round_id_(0) {}
void InitiateCollection();
static HeapStatisticsCollector* Instance();
private:
void CollectOnWorkerThread(scoped_refptr<base::TaskRunner> master,
int round_id);
void ReceiveStats(int round_id, size_t total_size, size_t used_size);
void SendStatsToBrowser(int round_id);
size_t total_bytes_;
size_t used_bytes_;
int workers_to_go_;
int round_id_;
};
HeapStatisticsCollector* HeapStatisticsCollector::Instance() {
CR_DEFINE_STATIC_LOCAL(HeapStatisticsCollector, instance, ());
return &instance;
}
void HeapStatisticsCollector::InitiateCollection() {
v8::HeapStatistics heap_stats;
v8::Isolate::GetCurrent()->GetHeapStatistics(&heap_stats);
total_bytes_ = heap_stats.total_heap_size();
used_bytes_ = heap_stats.used_heap_size();
base::Closure collect = base::Bind(
&HeapStatisticsCollector::CollectOnWorkerThread,
base::Unretained(this),
base::MessageLoopProxy::current(),
round_id_);
workers_to_go_ = RenderThread::Get()->PostTaskToAllWebWorkers(collect);
if (workers_to_go_) {
// The guard task to send out partial stats
// in case some workers are not responsive.
base::MessageLoopProxy::current()->PostDelayedTask(
FROM_HERE,
base::Bind(&HeapStatisticsCollector::SendStatsToBrowser,
base::Unretained(this),
round_id_),
base::TimeDelta::FromMilliseconds(kWaitForWorkersStatsTimeoutMS));
} else {
// No worker threads so just send out the main thread data right away.
SendStatsToBrowser(round_id_);
}
}
void HeapStatisticsCollector::CollectOnWorkerThread(
scoped_refptr<base::TaskRunner> master,
int round_id) {
size_t total_bytes = 0;
size_t used_bytes = 0;
v8::Isolate* isolate = v8::Isolate::GetCurrent();
if (isolate) {
v8::HeapStatistics heap_stats;
isolate->GetHeapStatistics(&heap_stats);
total_bytes = heap_stats.total_heap_size();
used_bytes = heap_stats.used_heap_size();
}
master->PostTask(
FROM_HERE,
base::Bind(&HeapStatisticsCollector::ReceiveStats,
base::Unretained(this),
round_id,
total_bytes,
used_bytes));
}
void HeapStatisticsCollector::ReceiveStats(int round_id,
size_t total_bytes,
size_t used_bytes) {
if (round_id != round_id_)
return;
total_bytes_ += total_bytes;
used_bytes_ += used_bytes;
if (!--workers_to_go_)
SendStatsToBrowser(round_id);
}
void HeapStatisticsCollector::SendStatsToBrowser(int round_id) {
if (round_id != round_id_)
return;
// TODO(alph): Do caching heap stats and use the cache if we haven't got
// reply from a worker.
// Currently a busy worker stats are not counted.
RenderThread::Get()->Send(new ChromeViewHostMsg_V8HeapStats(
total_bytes_, used_bytes_));
++round_id_;
}
} // namespace
bool ChromeRenderProcessObserver::is_incognito_process_ = false;
ChromeRenderProcessObserver::ChromeRenderProcessObserver(
ChromeContentRendererClient* client)
: client_(client),
clear_cache_pending_(false),
webkit_initialized_(false),
pending_cache_min_dead_capacity_(0),
pending_cache_max_dead_capacity_(0),
pending_cache_capacity_(kUnitializedCacheCapacity) {
const CommandLine& command_line = *CommandLine::ForCurrentProcess();
if (command_line.HasSwitch(switches::kEnableWatchdog)) {
// TODO(JAR): Need to implement renderer IO msgloop watchdog.
}
#if defined(ENABLE_AUTOFILL_DIALOG)
bool enable_autofill = !command_line.HasSwitch(
autofill::switches::kDisableInteractiveAutocomplete);
WebRuntimeFeatures::enableRequestAutocomplete(
enable_autofill ||
command_line.HasSwitch(switches::kEnableExperimentalWebPlatformFeatures));
#endif
RenderThread* thread = RenderThread::Get();
resource_delegate_.reset(new RendererResourceDelegate());
thread->SetResourceDispatcherDelegate(resource_delegate_.get());
// Configure modules that need access to resources.
net::NetModule::SetResourceProvider(chrome_common_net::NetResourceProvider);
#if defined(OS_WIN)
// Need to patch a few functions for font loading to work correctly.
base::FilePath pdf;
if (PathService::Get(chrome::FILE_PDF_PLUGIN, &pdf) &&
base::PathExists(pdf)) {
g_iat_patch_createdca.Patch(
pdf.value().c_str(), "gdi32.dll", "CreateDCA", CreateDCAPatch);
g_iat_patch_get_font_data.Patch(
pdf.value().c_str(), "gdi32.dll", "GetFontData", GetFontDataPatch);
}
#endif
#if defined(OS_POSIX) && !defined(OS_MACOSX) && defined(USE_NSS)
// On platforms where we use system NSS shared libraries,
// initialize NSS now because it won't be able to load the .so's
// after we engage the sandbox.
if (!command_line.HasSwitch(switches::kSingleProcess))
crypto::InitNSSSafely();
#elif defined(OS_WIN)
// crypt32.dll is used to decode X509 certificates for Chromoting.
// Only load this library when the feature is enabled.
std::string error;
base::LoadNativeLibrary(base::FilePath(L"crypt32.dll"), &error);
#endif
// Setup initial set of crash dump data for Field Trials in this renderer.
chrome_variations::SetChildProcessLoggingVariationList();
}
ChromeRenderProcessObserver::~ChromeRenderProcessObserver() {
}
bool ChromeRenderProcessObserver::OnControlMessageReceived(
const IPC::Message& message) {
bool handled = true;
IPC_BEGIN_MESSAGE_MAP(ChromeRenderProcessObserver, message)
IPC_MESSAGE_HANDLER(ChromeViewMsg_SetIsIncognitoProcess,
OnSetIsIncognitoProcess)
IPC_MESSAGE_HANDLER(ChromeViewMsg_SetCacheCapacities, OnSetCacheCapacities)
IPC_MESSAGE_HANDLER(ChromeViewMsg_ClearCache, OnClearCache)
IPC_MESSAGE_HANDLER(ChromeViewMsg_SetFieldTrialGroup, OnSetFieldTrialGroup)
IPC_MESSAGE_HANDLER(ChromeViewMsg_GetV8HeapStats, OnGetV8HeapStats)
IPC_MESSAGE_HANDLER(ChromeViewMsg_GetCacheResourceStats,
OnGetCacheResourceStats)
IPC_MESSAGE_HANDLER(ChromeViewMsg_PurgeMemory, OnPurgeMemory)
IPC_MESSAGE_HANDLER(ChromeViewMsg_SetContentSettingRules,
OnSetContentSettingRules)
IPC_MESSAGE_UNHANDLED(handled = false)
IPC_END_MESSAGE_MAP()
return handled;
}
void ChromeRenderProcessObserver::WebKitInitialized() {
webkit_initialized_ = true;
if (pending_cache_capacity_ != kUnitializedCacheCapacity) {
WebCache::setCapacities(pending_cache_min_dead_capacity_,
pending_cache_max_dead_capacity_,
pending_cache_capacity_);
}
// chrome-native: is a scheme used for placeholder navigations that allow
// UIs to be drawn with platform native widgets instead of HTML. These pages
// should not be accessible, and should also be treated as empty documents
// that can commit synchronously. No code should be runnable in these pages,
// so it should not need to access anything nor should it allow javascript
// URLs since it should never be visible to the user.
WebString native_scheme(ASCIIToUTF16(chrome::kChromeNativeScheme));
WebSecurityPolicy::registerURLSchemeAsDisplayIsolated(native_scheme);
WebSecurityPolicy::registerURLSchemeAsEmptyDocument(native_scheme);
WebSecurityPolicy::registerURLSchemeAsNoAccess(native_scheme);
WebSecurityPolicy::registerURLSchemeAsNotAllowingJavascriptURLs(
native_scheme);
}
void ChromeRenderProcessObserver::OnRenderProcessShutdown() {
webkit_initialized_ = false;
}
void ChromeRenderProcessObserver::OnSetIsIncognitoProcess(
bool is_incognito_process) {
is_incognito_process_ = is_incognito_process;
}
void ChromeRenderProcessObserver::OnSetContentSettingRules(
const RendererContentSettingRules& rules) {
content_setting_rules_ = rules;
}
void ChromeRenderProcessObserver::OnSetCacheCapacities(size_t min_dead_capacity,
size_t max_dead_capacity,
size_t capacity) {
if (!webkit_initialized_) {
pending_cache_min_dead_capacity_ = min_dead_capacity;
pending_cache_max_dead_capacity_ = max_dead_capacity;
pending_cache_capacity_ = capacity;
return;
}
WebCache::setCapacities(
min_dead_capacity, max_dead_capacity, capacity);
}
void ChromeRenderProcessObserver::OnClearCache(bool on_navigation) {
if (on_navigation || !webkit_initialized_) {
clear_cache_pending_ = true;
} else {
WebCache::clear();
}
}
void ChromeRenderProcessObserver::OnGetCacheResourceStats() {
WebCache::ResourceTypeStats stats;
if (webkit_initialized_)
WebCache::getResourceTypeStats(&stats);
RenderThread::Get()->Send(new ChromeViewHostMsg_ResourceTypeStats(stats));
}
void ChromeRenderProcessObserver::OnSetFieldTrialGroup(
const std::string& field_trial_name,
const std::string& group_name) {
base::FieldTrial* trial =
base::FieldTrialList::CreateFieldTrial(field_trial_name, group_name);
// Ensure the trial is marked as "used" by calling group() on it. This is
// needed to ensure the trial is properly reported in renderer crash reports.
trial->group();
chrome_variations::SetChildProcessLoggingVariationList();
}
void ChromeRenderProcessObserver::OnGetV8HeapStats() {
HeapStatisticsCollector::Instance()->InitiateCollection();
}
void ChromeRenderProcessObserver::OnPurgeMemory() {
if (!webkit_initialized_)
return;
// Clear the object cache (as much as possible; some live objects cannot be
// freed).
WebCache::clear();
// Clear the font/glyph cache.
WebFontCache::clear();
// Clear the Cross-Origin Preflight cache.
WebCrossOriginPreflightResultCache::clear();
// Release all freeable memory from the SQLite process-global page cache (a
// low-level object which backs the Connection-specific page caches).
while (sqlite3_release_memory(std::numeric_limits<int>::max()) > 0) {
}
v8::V8::LowMemoryNotification();
// Tell our allocator to release any free pages it's still holding.
base::allocator::ReleaseFreeMemory();
if (client_)
client_->OnPurgeMemory();
}
void ChromeRenderProcessObserver::ExecutePendingClearCache() {
if (clear_cache_pending_ && webkit_initialized_) {
clear_cache_pending_ = false;
WebCache::clear();
}
}
const RendererContentSettingRules*
ChromeRenderProcessObserver::content_setting_rules() const {
return &content_setting_rules_;
}