普通文本  |  417行  |  14.58 KB

// 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/net/net_resource_provider.h"
#include "chrome/common/render_messages.h"
#include "chrome/common/url_constants.h"
#include "chrome/common/variations/variations_util.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/WebDocument.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"

#if defined(OS_WIN)
#include "base/win/iat_patch_function.h"
#endif

using blink::WebCache;
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 content::RequestPeer* OnRequestComplete(
      content::RequestPeer* 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 content::RequestPeer* OnReceivedResponse(
      content::RequestPeer* 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)
  WebRuntimeFeatures::enableRequestAutocomplete(true);
#endif

  if (command_line.HasSwitch(switches::kEnableShowModalDialog))
    WebRuntimeFeatures::enableShowModalDialog(true);

  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.
  base::LoadNativeLibrary(base::FilePath(L"crypt32.dll"), NULL);
#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_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(base::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);
  // TODO(mef): Remove this check after the investigation of 359406 is complete.
  CHECK(trial) << 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::ExecutePendingClearCache() {
  if (clear_cache_pending_ && webkit_initialized_) {
    clear_cache_pending_ = false;
    WebCache::clear();
  }
}

const RendererContentSettingRules*
ChromeRenderProcessObserver::content_setting_rules() const {
  return &content_setting_rules_;
}