blob: 026b9e5db9301631849c5c7266ea87a568710703 [file] [log] [blame]
// Copyright (c) 2009 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/plugin/plugin_channel_base.h"
#include <stack>
#include "base/auto_reset.h"
#include "base/hash_tables.h"
#include "base/lazy_instance.h"
#include "chrome/common/child_process.h"
#include "ipc/ipc_sync_message.h"
#if defined(OS_POSIX)
#include "ipc/ipc_channel_posix.h"
#endif
typedef base::hash_map<std::string, scoped_refptr<PluginChannelBase> >
PluginChannelMap;
static PluginChannelMap g_plugin_channels_;
static base::LazyInstance<std::stack<scoped_refptr<PluginChannelBase> > >
lazy_plugin_channel_stack_(base::LINKER_INITIALIZED);
PluginChannelBase* PluginChannelBase::GetChannel(
const std::string& channel_name, IPC::Channel::Mode mode,
PluginChannelFactory factory, MessageLoop* ipc_message_loop,
bool create_pipe_now) {
scoped_refptr<PluginChannelBase> channel;
PluginChannelMap::const_iterator iter = g_plugin_channels_.find(channel_name);
if (iter == g_plugin_channels_.end()) {
channel = factory();
} else {
channel = iter->second;
}
DCHECK(channel != NULL);
if (!channel->channel_valid()) {
channel->channel_name_ = channel_name;
channel->mode_ = mode;
if (channel->Init(ipc_message_loop, create_pipe_now)) {
g_plugin_channels_[channel_name] = channel;
} else {
channel = NULL;
}
}
return channel;
}
void PluginChannelBase::Broadcast(IPC::Message* message) {
for (PluginChannelMap::iterator iter = g_plugin_channels_.begin();
iter != g_plugin_channels_.end();
++iter) {
iter->second->Send(new IPC::Message(*message));
}
delete message;
}
PluginChannelBase::PluginChannelBase()
: plugin_count_(0),
peer_pid_(0),
in_remove_route_(false),
channel_valid_(false),
in_sync_dispatch_(0),
send_unblocking_only_during_sync_dispatch_(false) {
}
PluginChannelBase::~PluginChannelBase() {
}
PluginChannelBase* PluginChannelBase::GetCurrentChannel() {
return lazy_plugin_channel_stack_.Pointer()->top();
}
void PluginChannelBase::CleanupChannels() {
// Make a copy of the references as we can't iterate the map since items will
// be removed from it as we clean them up.
std::vector<scoped_refptr<PluginChannelBase> > channels;
for (PluginChannelMap::const_iterator iter = g_plugin_channels_.begin();
iter != g_plugin_channels_.end();
++iter) {
channels.push_back(iter->second);
}
for (size_t i = 0; i < channels.size(); ++i)
channels[i]->CleanUp();
// This will clean up channels added to the map for which subsequent
// AddRoute wasn't called
g_plugin_channels_.clear();
}
bool PluginChannelBase::Init(MessageLoop* ipc_message_loop,
bool create_pipe_now) {
channel_.reset(new IPC::SyncChannel(
channel_name_, mode_, this, NULL, ipc_message_loop, create_pipe_now,
ChildProcess::current()->GetShutDownEvent()));
channel_valid_ = true;
return true;
}
bool PluginChannelBase::Send(IPC::Message* message) {
if (!channel_.get()) {
delete message;
return false;
}
if (send_unblocking_only_during_sync_dispatch_ && in_sync_dispatch_ == 0 &&
message->is_sync()) {
message->set_unblock(false);
}
return channel_->Send(message);
}
int PluginChannelBase::Count() {
return static_cast<int>(g_plugin_channels_.size());
}
void PluginChannelBase::OnMessageReceived(const IPC::Message& message) {
// This call might cause us to be deleted, so keep an extra reference to
// ourself so that we can send the reply and decrement back in_dispatch_.
lazy_plugin_channel_stack_.Pointer()->push(
scoped_refptr<PluginChannelBase>(this));
if (message.is_sync())
in_sync_dispatch_++;
if (message.routing_id() == MSG_ROUTING_CONTROL) {
OnControlMessageReceived(message);
} else {
bool routed = router_.RouteMessage(message);
if (!routed && message.is_sync()) {
// The listener has gone away, so we must respond or else the caller will
// hang waiting for a reply.
IPC::Message* reply = IPC::SyncMessage::GenerateReply(&message);
reply->set_reply_error();
Send(reply);
}
}
if (message.is_sync())
in_sync_dispatch_--;
lazy_plugin_channel_stack_.Pointer()->pop();
}
void PluginChannelBase::OnChannelConnected(int32 peer_pid) {
peer_pid_ = peer_pid;
}
void PluginChannelBase::AddRoute(int route_id,
IPC::Channel::Listener* listener,
bool npobject) {
if (npobject) {
npobject_listeners_[route_id] = listener;
} else {
plugin_count_++;
}
router_.AddRoute(route_id, listener);
}
void PluginChannelBase::RemoveRoute(int route_id) {
router_.RemoveRoute(route_id);
ListenerMap::iterator iter = npobject_listeners_.find(route_id);
if (iter != npobject_listeners_.end()) {
// This was an NPObject proxy or stub, it's not involved in the refcounting.
// If this RemoveRoute call from the NPObject is a result of us calling
// OnChannelError below, don't call erase() here because that'll corrupt
// the iterator below.
if (in_remove_route_) {
iter->second = NULL;
} else {
npobject_listeners_.erase(iter);
}
return;
}
plugin_count_--;
DCHECK(plugin_count_ >= 0);
if (!plugin_count_) {
AutoReset auto_reset_in_remove_route(&in_remove_route_, true);
for (ListenerMap::iterator npobj_iter = npobject_listeners_.begin();
npobj_iter != npobject_listeners_.end(); ++npobj_iter) {
if (npobj_iter->second)
npobj_iter->second->OnChannelError();
}
for (PluginChannelMap::iterator iter = g_plugin_channels_.begin();
iter != g_plugin_channels_.end(); ++iter) {
if (iter->second == this) {
#if defined(OS_POSIX)
if (channel_valid()) {
IPC::RemoveAndCloseChannelSocket(channel_name());
}
#endif
g_plugin_channels_.erase(iter);
return;
}
}
NOTREACHED();
}
}
void PluginChannelBase::OnControlMessageReceived(const IPC::Message& msg) {
NOTREACHED() <<
"should override in subclass if you care about control messages";
}
void PluginChannelBase::OnChannelError() {
#if defined(OS_POSIX)
if (channel_valid()) {
IPC::RemoveAndCloseChannelSocket(channel_name());
}
#endif
channel_valid_ = false;
}
void PluginChannelBase::SendUnblockingOnlyDuringSyncDispatch() {
send_unblocking_only_during_sync_dispatch_ = true;
}