// 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 "content/plugin/plugin_channel.h" #include "base/bind.h" #include "base/command_line.h" #include "base/process/process_handle.h" #include "base/strings/string_util.h" #include "base/synchronization/lock.h" #include "base/synchronization/waitable_event.h" #include "build/build_config.h" #include "content/child/child_process.h" #include "content/child/npapi/plugin_instance.h" #include "content/child/npapi/webplugin_delegate_impl.h" #include "content/child/plugin_messages.h" #include "content/common/plugin_process_messages.h" #include "content/plugin/plugin_thread.h" #include "content/plugin/webplugin_delegate_stub.h" #include "content/plugin/webplugin_proxy.h" #include "content/public/common/content_switches.h" #include "third_party/WebKit/public/web/WebBindings.h" #if defined(OS_POSIX) #include "ipc/ipc_channel_posix.h" #endif using blink::WebBindings; namespace content { namespace { // How long we wait before releasing the plugin process. const int kPluginReleaseTimeMinutes = 5; } // namespace // If a sync call to the renderer results in a modal dialog, we need to have a // way to know so that we can run a nested message loop to simulate what would // happen in a single process browser and avoid deadlock. class PluginChannel::MessageFilter : public IPC::ChannelProxy::MessageFilter { public: MessageFilter() : channel_(NULL) { } base::WaitableEvent* GetModalDialogEvent(int render_view_id) { base::AutoLock auto_lock(modal_dialog_event_map_lock_); if (!modal_dialog_event_map_.count(render_view_id)) { NOTREACHED(); return NULL; } return modal_dialog_event_map_[render_view_id].event; } // Decrement the ref count associated with the modal dialog event for the // given tab. void ReleaseModalDialogEvent(int render_view_id) { base::AutoLock auto_lock(modal_dialog_event_map_lock_); if (!modal_dialog_event_map_.count(render_view_id)) { NOTREACHED(); return; } if (--(modal_dialog_event_map_[render_view_id].refcount)) return; // Delete the event when the stack unwinds as it could be in use now. base::MessageLoop::current()->DeleteSoon( FROM_HERE, modal_dialog_event_map_[render_view_id].event); modal_dialog_event_map_.erase(render_view_id); } bool Send(IPC::Message* message) { // Need this function for the IPC_MESSAGE_HANDLER_DELAY_REPLY macro. return channel_->Send(message); } // IPC::ChannelProxy::MessageFilter: virtual void OnFilterAdded(IPC::Channel* channel) OVERRIDE { channel_ = channel; } virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE { IPC_BEGIN_MESSAGE_MAP(PluginChannel::MessageFilter, message) IPC_MESSAGE_HANDLER_DELAY_REPLY(PluginMsg_Init, OnInit) IPC_MESSAGE_HANDLER(PluginMsg_SignalModalDialogEvent, OnSignalModalDialogEvent) IPC_MESSAGE_HANDLER(PluginMsg_ResetModalDialogEvent, OnResetModalDialogEvent) IPC_END_MESSAGE_MAP() return message.type() == PluginMsg_SignalModalDialogEvent::ID || message.type() == PluginMsg_ResetModalDialogEvent::ID; } protected: virtual ~MessageFilter() { // Clean up in case of renderer crash. for (ModalDialogEventMap::iterator i = modal_dialog_event_map_.begin(); i != modal_dialog_event_map_.end(); ++i) { delete i->second.event; } } private: void OnInit(const PluginMsg_Init_Params& params, IPC::Message* reply_msg) { base::AutoLock auto_lock(modal_dialog_event_map_lock_); if (modal_dialog_event_map_.count(params.host_render_view_routing_id)) { modal_dialog_event_map_[params.host_render_view_routing_id].refcount++; return; } WaitableEventWrapper wrapper; wrapper.event = new base::WaitableEvent(true, false); wrapper.refcount = 1; modal_dialog_event_map_[params.host_render_view_routing_id] = wrapper; } void OnSignalModalDialogEvent(int render_view_id) { base::AutoLock auto_lock(modal_dialog_event_map_lock_); if (modal_dialog_event_map_.count(render_view_id)) modal_dialog_event_map_[render_view_id].event->Signal(); } void OnResetModalDialogEvent(int render_view_id) { base::AutoLock auto_lock(modal_dialog_event_map_lock_); if (modal_dialog_event_map_.count(render_view_id)) modal_dialog_event_map_[render_view_id].event->Reset(); } struct WaitableEventWrapper { base::WaitableEvent* event; int refcount; // There could be multiple plugin instances per tab. }; typedef std::map<int, WaitableEventWrapper> ModalDialogEventMap; ModalDialogEventMap modal_dialog_event_map_; base::Lock modal_dialog_event_map_lock_; IPC::Channel* channel_; }; PluginChannel* PluginChannel::GetPluginChannel( int renderer_id, base::MessageLoopProxy* ipc_message_loop) { // Map renderer ID to a (single) channel to that process. std::string channel_key = base::StringPrintf( "%d.r%d", base::GetCurrentProcId(), renderer_id); PluginChannel* channel = static_cast<PluginChannel*>(NPChannelBase::GetChannel( channel_key, IPC::Channel::MODE_SERVER, ClassFactory, ipc_message_loop, false, ChildProcess::current()->GetShutDownEvent())); if (channel) channel->renderer_id_ = renderer_id; return channel; } // static void PluginChannel::NotifyRenderersOfPendingShutdown() { Broadcast(new PluginHostMsg_PluginShuttingDown()); } bool PluginChannel::Send(IPC::Message* msg) { in_send_++; if (log_messages_) { VLOG(1) << "sending message @" << msg << " on channel @" << this << " with type " << msg->type(); } bool result = NPChannelBase::Send(msg); in_send_--; return result; } bool PluginChannel::OnMessageReceived(const IPC::Message& msg) { if (log_messages_) { VLOG(1) << "received message @" << &msg << " on channel @" << this << " with type " << msg.type(); } return NPChannelBase::OnMessageReceived(msg); } void PluginChannel::OnChannelError() { NPChannelBase::OnChannelError(); CleanUp(); } int PluginChannel::GenerateRouteID() { static int last_id = 0; return ++last_id; } base::WaitableEvent* PluginChannel::GetModalDialogEvent(int render_view_id) { return filter_->GetModalDialogEvent(render_view_id); } PluginChannel::~PluginChannel() { PluginThread::current()->Send(new PluginProcessHostMsg_ChannelDestroyed( renderer_id_)); process_ref_.ReleaseWithDelay( base::TimeDelta::FromMinutes(kPluginReleaseTimeMinutes)); } void PluginChannel::CleanUp() { // We need to clean up the stubs so that they call NPPDestroy. This will // also lead to them releasing their reference on this object so that it can // be deleted. for (size_t i = 0; i < plugin_stubs_.size(); ++i) RemoveRoute(plugin_stubs_[i]->instance_id()); // Need to addref this object temporarily because otherwise removing the last // stub will cause the destructor of this object to be called, however at // that point plugin_stubs_ will have one element and its destructor will be // called twice. scoped_refptr<PluginChannel> me(this); while (!plugin_stubs_.empty()) { // Separate vector::erase and ~WebPluginDelegateStub. // See https://code.google.com/p/chromium/issues/detail?id=314088 scoped_refptr<WebPluginDelegateStub> stub = plugin_stubs_[0]; plugin_stubs_.erase(plugin_stubs_.begin()); } } bool PluginChannel::Init(base::MessageLoopProxy* ipc_message_loop, bool create_pipe_now, base::WaitableEvent* shutdown_event) { if (!NPChannelBase::Init(ipc_message_loop, create_pipe_now, shutdown_event)) return false; channel_->AddFilter(filter_.get()); return true; } PluginChannel::PluginChannel() : renderer_id_(-1), in_send_(0), incognito_(false), filter_(new MessageFilter()), npp_(new struct _NPP) { set_send_unblocking_only_during_unblock_dispatch(); const CommandLine* command_line = CommandLine::ForCurrentProcess(); log_messages_ = command_line->HasSwitch(switches::kLogPluginMessages); // Register |npp_| as the default owner for any object we receive via IPC, // and register it with WebBindings as a valid owner. SetDefaultNPObjectOwner(npp_.get()); WebBindings::registerObjectOwner(npp_.get()); } bool PluginChannel::OnControlMessageReceived(const IPC::Message& msg) { bool handled = true; IPC_BEGIN_MESSAGE_MAP(PluginChannel, msg) IPC_MESSAGE_HANDLER(PluginMsg_CreateInstance, OnCreateInstance) IPC_MESSAGE_HANDLER_DELAY_REPLY(PluginMsg_DestroyInstance, OnDestroyInstance) IPC_MESSAGE_HANDLER(PluginMsg_GenerateRouteID, OnGenerateRouteID) IPC_MESSAGE_HANDLER(PluginProcessMsg_ClearSiteData, OnClearSiteData) IPC_MESSAGE_HANDLER(PluginHostMsg_DidAbortLoading, OnDidAbortLoading) IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP() DCHECK(handled); return handled; } void PluginChannel::OnCreateInstance(const std::string& mime_type, int* instance_id) { *instance_id = GenerateRouteID(); scoped_refptr<WebPluginDelegateStub> stub(new WebPluginDelegateStub( mime_type, *instance_id, this)); AddRoute(*instance_id, stub.get(), NULL); plugin_stubs_.push_back(stub); } void PluginChannel::OnDestroyInstance(int instance_id, IPC::Message* reply_msg) { for (size_t i = 0; i < plugin_stubs_.size(); ++i) { if (plugin_stubs_[i]->instance_id() == instance_id) { scoped_refptr<MessageFilter> filter(filter_); int render_view_id = plugin_stubs_[i]->webplugin()->host_render_view_routing_id(); // Separate vector::erase and ~WebPluginDelegateStub. // See https://code.google.com/p/chromium/issues/detail?id=314088 scoped_refptr<WebPluginDelegateStub> stub = plugin_stubs_[i]; plugin_stubs_.erase(plugin_stubs_.begin() + i); stub = NULL; Send(reply_msg); RemoveRoute(instance_id); // NOTE: *this* might be deleted as a result of calling RemoveRoute. // Don't release the modal dialog event right away, but do it after the // stack unwinds since the plugin can be destroyed later if it's in use // right now. base::MessageLoop::current()->PostNonNestableTask( FROM_HERE, base::Bind(&MessageFilter::ReleaseModalDialogEvent, filter.get(), render_view_id)); return; } } NOTREACHED() << "Couldn't find WebPluginDelegateStub to destroy"; } void PluginChannel::OnGenerateRouteID(int* route_id) { *route_id = GenerateRouteID(); } void PluginChannel::OnClearSiteData(const std::string& site, uint64 flags, uint64 max_age) { bool success = false; CommandLine* command_line = CommandLine::ForCurrentProcess(); base::FilePath path = command_line->GetSwitchValuePath(switches::kPluginPath); scoped_refptr<PluginLib> plugin_lib(PluginLib::CreatePluginLib(path)); if (plugin_lib.get()) { NPError err = plugin_lib->NP_Initialize(); if (err == NPERR_NO_ERROR) { const char* site_str = site.empty() ? NULL : site.c_str(); err = plugin_lib->NP_ClearSiteData(site_str, flags, max_age); std::string site_name = site.empty() ? "NULL" : base::StringPrintf("\"%s\"", site_str); VLOG(1) << "NPP_ClearSiteData(" << site_name << ", " << flags << ", " << max_age << ") returned " << err; success = (err == NPERR_NO_ERROR); } } Send(new PluginProcessHostMsg_ClearSiteDataResult(success)); } void PluginChannel::OnDidAbortLoading(int render_view_id) { for (size_t i = 0; i < plugin_stubs_.size(); ++i) { if (plugin_stubs_[i]->webplugin()->host_render_view_routing_id() == render_view_id) { plugin_stubs_[i]->delegate()->instance()->CloseStreams(); } } } } // namespace content