blob: 65a9b7a228a39493a655d6745dee204245df2be9 [file] [log] [blame]
// Copyright (c) 2011 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 "content/common/child_process_host.h"
#include "base/command_line.h"
#include "base/file_path.h"
#include "base/logging.h"
#include "base/metrics/histogram.h"
#include "base/path_service.h"
#include "base/third_party/dynamic_annotations/dynamic_annotations.h"
#include "content/common/child_process_info.h"
#include "content/common/child_process_messages.h"
#include "content/common/content_paths.h"
#include "content/common/content_switches.h"
#include "ipc/ipc_logging.h"
#if defined(OS_LINUX)
#include "base/linux_util.h"
#endif // OS_LINUX
#if defined(OS_MACOSX)
namespace {
// Given |path| identifying a Mac-style child process executable path, adjusts
// it to correspond to |feature|. For a child process path such as
// ".../Chromium Helper.app/Contents/MacOS/Chromium Helper", the transformed
// path for feature "NP" would be
// ".../Chromium Helper NP.app/Contents/MacOS/Chromium Helper NP". The new
// path is returned.
FilePath TransformPathForFeature(const FilePath& path,
const std::string& feature) {
std::string basename = path.BaseName().value();
FilePath macos_path = path.DirName();
const char kMacOSName[] = "MacOS";
DCHECK_EQ(kMacOSName, macos_path.BaseName().value());
FilePath contents_path = macos_path.DirName();
const char kContentsName[] = "Contents";
DCHECK_EQ(kContentsName, contents_path.BaseName().value());
FilePath helper_app_path = contents_path.DirName();
const char kAppExtension[] = ".app";
std::string basename_app = basename;
basename_app.append(kAppExtension);
DCHECK_EQ(basename_app, helper_app_path.BaseName().value());
FilePath root_path = helper_app_path.DirName();
std::string new_basename = basename;
new_basename.append(1, ' ');
new_basename.append(feature);
std::string new_basename_app = new_basename;
new_basename_app.append(kAppExtension);
FilePath new_path = root_path.Append(new_basename_app)
.Append(kContentsName)
.Append(kMacOSName)
.Append(new_basename);
return new_path;
}
} // namespace
#endif // OS_MACOSX
#if defined (OS_WIN)
// Types used in PreCacheFont
namespace {
typedef std::vector<string16> FontNameVector;
typedef std::map<int, FontNameVector> PidToFontNames;
}
#endif // OS_WIN
ChildProcessHost::ChildProcessHost()
: ALLOW_THIS_IN_INITIALIZER_LIST(listener_(this)),
opening_channel_(false) {
}
ChildProcessHost::~ChildProcessHost() {
for (size_t i = 0; i < filters_.size(); ++i) {
filters_[i]->OnChannelClosing();
filters_[i]->OnFilterRemoved();
}
listener_.Shutdown();
}
void ChildProcessHost::AddFilter(IPC::ChannelProxy::MessageFilter* filter) {
filters_.push_back(filter);
if (channel_.get())
filter->OnFilterAdded(channel_.get());
}
// static
FilePath ChildProcessHost::GetChildPath(int flags) {
FilePath child_path;
child_path = CommandLine::ForCurrentProcess()->GetSwitchValuePath(
switches::kBrowserSubprocessPath);
#if defined(OS_LINUX)
// Use /proc/self/exe rather than our known binary path so updates
// can't swap out the binary from underneath us.
// When running under Valgrind, forking /proc/self/exe ends up forking the
// Valgrind executable, which then crashes. However, it's almost safe to
// assume that the updates won't happen while testing with Valgrind tools.
if (child_path.empty() && flags & CHILD_ALLOW_SELF && !RunningOnValgrind())
child_path = FilePath("/proc/self/exe");
#endif
// On most platforms, the child executable is the same as the current
// executable.
if (child_path.empty())
PathService::Get(content::CHILD_PROCESS_EXE, &child_path);
#if defined(OS_MACOSX)
DCHECK(!(flags & CHILD_NO_PIE && flags & CHILD_ALLOW_HEAP_EXECUTION));
// If needed, choose an executable with special flags set that inform the
// kernel to enable or disable specific optional process-wide features.
if (flags & CHILD_NO_PIE) {
// "NP" is "No PIE". This results in Chromium Helper NP.app or
// Google Chrome Helper NP.app.
child_path = TransformPathForFeature(child_path, "NP");
} else if (flags & CHILD_ALLOW_HEAP_EXECUTION) {
// "EH" is "Executable Heap". A non-executable heap is only available to
// 32-bit processes on Mac OS X 10.7. Most code can and should run with a
// non-executable heap, but the "EH" feature is provided to allow code
// intolerant of a non-executable heap to work properly on 10.7. This
// results in Chromium Helper EH.app or Google Chrome Helper EH.app.
child_path = TransformPathForFeature(child_path, "EH");
}
#endif
return child_path;
}
#if defined(OS_WIN)
ChildProcessHost::FontCache::CacheElement::CacheElement()
: font_(NULL), dc_(NULL), ref_count_(0) {
}
ChildProcessHost::FontCache::CacheElement::~CacheElement() {
if (font_) {
DeleteObject(font_);
}
if (dc_) {
DeleteDC(dc_);
}
}
ChildProcessHost::FontCache::FontCache() {
}
ChildProcessHost::FontCache::~FontCache() {
}
// static
ChildProcessHost::FontCache* ChildProcessHost::FontCache::GetInstance() {
return Singleton<ChildProcessHost::FontCache>::get();
}
void ChildProcessHost::FontCache::PreCacheFont(LOGFONT font, int process_id) {
typedef std::map<string16, ChildProcessHost::FontCache::CacheElement>
FontNameToElement;
base::AutoLock lock(mutex_);
// Fetch the font into memory.
// No matter the font is cached or not, we load it to avoid GDI swapping out
// that font file.
HDC hdc = GetDC(NULL);
HFONT font_handle = CreateFontIndirect(&font);
DCHECK(NULL != font_handle);
HGDIOBJ old_font = SelectObject(hdc, font_handle);
DCHECK(NULL != old_font);
TEXTMETRIC tm;
BOOL ret = GetTextMetrics(hdc, &tm);
DCHECK(ret);
string16 font_name = font.lfFaceName;
int ref_count_inc = 1;
FontNameVector::iterator it =
std::find(process_id_font_map_[process_id].begin(),
process_id_font_map_[process_id].end(),
font_name);
if (it == process_id_font_map_[process_id].end()) {
// Requested font is new to cache.
process_id_font_map_[process_id].push_back(font_name);
} else {
ref_count_inc = 0;
}
if (cache_[font_name].ref_count_ == 0) { // Requested font is new to cache.
cache_[font_name].ref_count_ = 1;
} else { // Requested font is already in cache, release old handles.
DeleteObject(cache_[font_name].font_);
DeleteDC(cache_[font_name].dc_);
}
cache_[font_name].font_ = font_handle;
cache_[font_name].dc_ = hdc;
cache_[font_name].ref_count_ += ref_count_inc;
}
void ChildProcessHost::FontCache::ReleaseCachedFonts(int process_id) {
typedef std::map<string16, ChildProcessHost::FontCache::CacheElement>
FontNameToElement;
base::AutoLock lock(mutex_);
PidToFontNames::iterator it;
it = process_id_font_map_.find(process_id);
if (it == process_id_font_map_.end()) {
return;
}
for (FontNameVector::iterator i = it->second.begin(), e = it->second.end();
i != e; ++i) {
FontNameToElement::iterator element;
element = cache_.find(*i);
if (element != cache_.end()) {
--((*element).second.ref_count_);
}
}
process_id_font_map_.erase(it);
for (FontNameToElement::iterator i = cache_.begin(); i != cache_.end(); ) {
if (i->second.ref_count_ == 0) {
cache_.erase(i++);
} else {
++i;
}
}
}
// static
void ChildProcessHost::PreCacheFont(LOGFONT font, int pid) {
// If a child process is running in a sandbox, GetTextMetrics()
// can sometimes fail. If a font has not been loaded
// previously, GetTextMetrics() will try to load the font
// from the font file. However, the sandboxed process does
// not have permissions to access any font files and
// the call fails. So we make the browser pre-load the
// font for us by using a dummy call to GetTextMetrics of
// the same font.
// This means the browser process just loads the font into memory so that
// when GDI attempt to query that font info in child process, it does not
// need to load that file, hence no permission issues there. Therefore,
// when a font is asked to be cached, we always recreates the font object
// to avoid the case that an in-cache font is swapped out by GDI.
ChildProcessHost::FontCache::GetInstance()->PreCacheFont(font, pid);
}
// static
void ChildProcessHost::ReleaseCachedFonts(int pid) {
// Release cached fonts that requested from a pid by decrementing the ref
// count. When ref count is zero, the handles are released.
ChildProcessHost::FontCache::GetInstance()->ReleaseCachedFonts(pid);
}
#endif // OS_WIN
bool ChildProcessHost::CreateChannel() {
channel_id_ = ChildProcessInfo::GenerateRandomChannelID(this);
channel_.reset(new IPC::Channel(
channel_id_, IPC::Channel::MODE_SERVER, &listener_));
if (!channel_->Connect())
return false;
for (size_t i = 0; i < filters_.size(); ++i)
filters_[i]->OnFilterAdded(channel_.get());
// Make sure these messages get sent first.
#if defined(IPC_MESSAGE_LOG_ENABLED)
bool enabled = IPC::Logging::GetInstance()->Enabled();
Send(new ChildProcessMsg_SetIPCLoggingEnabled(enabled));
#endif
Send(new ChildProcessMsg_AskBeforeShutdown());
opening_channel_ = true;
return true;
}
void ChildProcessHost::InstanceCreated() {
Notify(content::NOTIFICATION_CHILD_INSTANCE_CREATED);
}
bool ChildProcessHost::OnMessageReceived(const IPC::Message& msg) {
return false;
}
void ChildProcessHost::OnChannelConnected(int32 peer_pid) {
}
void ChildProcessHost::OnChannelError() {
}
bool ChildProcessHost::Send(IPC::Message* message) {
if (!channel_.get()) {
delete message;
return false;
}
return channel_->Send(message);
}
void ChildProcessHost::OnChildDied() {
delete this;
}
void ChildProcessHost::OnChildDisconnected() {
OnChildDied();
}
void ChildProcessHost::ShutdownStarted() {
}
void ChildProcessHost::Notify(int type) {
}
ChildProcessHost::ListenerHook::ListenerHook(ChildProcessHost* host)
: host_(host) {
}
void ChildProcessHost::ListenerHook::Shutdown() {
host_ = NULL;
}
bool ChildProcessHost::ListenerHook::OnMessageReceived(
const IPC::Message& msg) {
if (!host_)
return true;
#ifdef IPC_MESSAGE_LOG_ENABLED
IPC::Logging* logger = IPC::Logging::GetInstance();
if (msg.type() == IPC_LOGGING_ID) {
logger->OnReceivedLoggingMessage(msg);
return true;
}
if (logger->Enabled())
logger->OnPreDispatchMessage(msg);
#endif
bool handled = false;
for (size_t i = 0; i < host_->filters_.size(); ++i) {
if (host_->filters_[i]->OnMessageReceived(msg)) {
handled = true;
break;
}
}
if (!handled && msg.type() == ChildProcessHostMsg_ShutdownRequest::ID) {
if (host_->CanShutdown())
host_->Send(new ChildProcessMsg_Shutdown());
handled = true;
}
if (!handled)
handled = host_->OnMessageReceived(msg);
#ifdef IPC_MESSAGE_LOG_ENABLED
if (logger->Enabled())
logger->OnPostDispatchMessage(msg, host_->channel_id_);
#endif
return handled;
}
void ChildProcessHost::ListenerHook::OnChannelConnected(int32 peer_pid) {
if (!host_)
return;
host_->opening_channel_ = false;
host_->OnChannelConnected(peer_pid);
// Notify in the main loop of the connection.
host_->Notify(content::NOTIFICATION_CHILD_PROCESS_HOST_CONNECTED);
for (size_t i = 0; i < host_->filters_.size(); ++i)
host_->filters_[i]->OnChannelConnected(peer_pid);
}
void ChildProcessHost::ListenerHook::OnChannelError() {
if (!host_)
return;
host_->opening_channel_ = false;
host_->OnChannelError();
for (size_t i = 0; i < host_->filters_.size(); ++i)
host_->filters_[i]->OnChannelError();
// This will delete host_, which will also destroy this!
host_->OnChildDisconnected();
}
void ChildProcessHost::ForceShutdown() {
Send(new ChildProcessMsg_Shutdown());
}