Home | History | Annotate | Download | only in plugin
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "content/plugin/plugin_channel.h"
      6 
      7 #include "base/bind.h"
      8 #include "base/command_line.h"
      9 #include "base/process/process_handle.h"
     10 #include "base/strings/string_util.h"
     11 #include "base/synchronization/lock.h"
     12 #include "base/synchronization/waitable_event.h"
     13 #include "build/build_config.h"
     14 #include "content/child/child_process.h"
     15 #include "content/child/npapi/plugin_instance.h"
     16 #include "content/child/plugin_messages.h"
     17 #include "content/common/plugin_process_messages.h"
     18 #include "content/plugin/plugin_thread.h"
     19 #include "content/plugin/webplugin_delegate_stub.h"
     20 #include "content/plugin/webplugin_proxy.h"
     21 #include "content/public/common/content_switches.h"
     22 #include "third_party/WebKit/public/web/WebBindings.h"
     23 
     24 #if defined(OS_POSIX)
     25 #include "base/posix/eintr_wrapper.h"
     26 #include "ipc/ipc_channel_posix.h"
     27 #endif
     28 
     29 using WebKit::WebBindings;
     30 
     31 namespace content {
     32 
     33 namespace {
     34 
     35 void PluginReleaseCallback() {
     36   ChildProcess::current()->ReleaseProcess();
     37 }
     38 
     39 // How long we wait before releasing the plugin process.
     40 const int kPluginReleaseTimeMinutes = 5;
     41 
     42 }  // namespace
     43 
     44 // If a sync call to the renderer results in a modal dialog, we need to have a
     45 // way to know so that we can run a nested message loop to simulate what would
     46 // happen in a single process browser and avoid deadlock.
     47 class PluginChannel::MessageFilter : public IPC::ChannelProxy::MessageFilter {
     48  public:
     49   MessageFilter() : channel_(NULL) { }
     50 
     51   base::WaitableEvent* GetModalDialogEvent(int render_view_id) {
     52     base::AutoLock auto_lock(modal_dialog_event_map_lock_);
     53     if (!modal_dialog_event_map_.count(render_view_id)) {
     54       NOTREACHED();
     55       return NULL;
     56     }
     57 
     58     return modal_dialog_event_map_[render_view_id].event;
     59   }
     60 
     61   // Decrement the ref count associated with the modal dialog event for the
     62   // given tab.
     63   void ReleaseModalDialogEvent(int render_view_id) {
     64     base::AutoLock auto_lock(modal_dialog_event_map_lock_);
     65     if (!modal_dialog_event_map_.count(render_view_id)) {
     66       NOTREACHED();
     67       return;
     68     }
     69 
     70     if (--(modal_dialog_event_map_[render_view_id].refcount))
     71       return;
     72 
     73     // Delete the event when the stack unwinds as it could be in use now.
     74     base::MessageLoop::current()->DeleteSoon(
     75         FROM_HERE, modal_dialog_event_map_[render_view_id].event);
     76     modal_dialog_event_map_.erase(render_view_id);
     77   }
     78 
     79   bool Send(IPC::Message* message) {
     80     // Need this function for the IPC_MESSAGE_HANDLER_DELAY_REPLY macro.
     81     return channel_->Send(message);
     82   }
     83 
     84   // IPC::ChannelProxy::MessageFilter:
     85   virtual void OnFilterAdded(IPC::Channel* channel) OVERRIDE {
     86     channel_ = channel;
     87   }
     88 
     89   virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE {
     90     IPC_BEGIN_MESSAGE_MAP(PluginChannel::MessageFilter, message)
     91       IPC_MESSAGE_HANDLER_DELAY_REPLY(PluginMsg_Init, OnInit)
     92       IPC_MESSAGE_HANDLER(PluginMsg_SignalModalDialogEvent,
     93                           OnSignalModalDialogEvent)
     94       IPC_MESSAGE_HANDLER(PluginMsg_ResetModalDialogEvent,
     95                           OnResetModalDialogEvent)
     96     IPC_END_MESSAGE_MAP()
     97     return message.type() == PluginMsg_SignalModalDialogEvent::ID ||
     98            message.type() == PluginMsg_ResetModalDialogEvent::ID;
     99   }
    100 
    101  protected:
    102   virtual ~MessageFilter() {
    103     // Clean up in case of renderer crash.
    104     for (ModalDialogEventMap::iterator i = modal_dialog_event_map_.begin();
    105         i != modal_dialog_event_map_.end(); ++i) {
    106       delete i->second.event;
    107     }
    108   }
    109 
    110  private:
    111   void OnInit(const PluginMsg_Init_Params& params, IPC::Message* reply_msg) {
    112     base::AutoLock auto_lock(modal_dialog_event_map_lock_);
    113     if (modal_dialog_event_map_.count(params.host_render_view_routing_id)) {
    114       modal_dialog_event_map_[params.host_render_view_routing_id].refcount++;
    115       return;
    116     }
    117 
    118     WaitableEventWrapper wrapper;
    119     wrapper.event = new base::WaitableEvent(true, false);
    120     wrapper.refcount = 1;
    121     modal_dialog_event_map_[params.host_render_view_routing_id] = wrapper;
    122   }
    123 
    124   void OnSignalModalDialogEvent(int render_view_id) {
    125     base::AutoLock auto_lock(modal_dialog_event_map_lock_);
    126     if (modal_dialog_event_map_.count(render_view_id))
    127       modal_dialog_event_map_[render_view_id].event->Signal();
    128   }
    129 
    130   void OnResetModalDialogEvent(int render_view_id) {
    131     base::AutoLock auto_lock(modal_dialog_event_map_lock_);
    132     if (modal_dialog_event_map_.count(render_view_id))
    133       modal_dialog_event_map_[render_view_id].event->Reset();
    134   }
    135 
    136   struct WaitableEventWrapper {
    137     base::WaitableEvent* event;
    138     int refcount;  // There could be multiple plugin instances per tab.
    139   };
    140   typedef std::map<int, WaitableEventWrapper> ModalDialogEventMap;
    141   ModalDialogEventMap modal_dialog_event_map_;
    142   base::Lock modal_dialog_event_map_lock_;
    143 
    144   IPC::Channel* channel_;
    145 };
    146 
    147 PluginChannel* PluginChannel::GetPluginChannel(
    148     int renderer_id, base::MessageLoopProxy* ipc_message_loop) {
    149   // Map renderer ID to a (single) channel to that process.
    150   std::string channel_key = base::StringPrintf(
    151       "%d.r%d", base::GetCurrentProcId(), renderer_id);
    152 
    153   PluginChannel* channel =
    154       static_cast<PluginChannel*>(NPChannelBase::GetChannel(
    155           channel_key,
    156           IPC::Channel::MODE_SERVER,
    157           ClassFactory,
    158           ipc_message_loop,
    159           false,
    160           ChildProcess::current()->GetShutDownEvent()));
    161 
    162   if (channel)
    163     channel->renderer_id_ = renderer_id;
    164 
    165   return channel;
    166 }
    167 
    168 // static
    169 void PluginChannel::NotifyRenderersOfPendingShutdown() {
    170   Broadcast(new PluginHostMsg_PluginShuttingDown());
    171 }
    172 
    173 bool PluginChannel::Send(IPC::Message* msg) {
    174   in_send_++;
    175   if (log_messages_) {
    176     VLOG(1) << "sending message @" << msg << " on channel @" << this
    177             << " with type " << msg->type();
    178   }
    179   bool result = NPChannelBase::Send(msg);
    180   in_send_--;
    181   return result;
    182 }
    183 
    184 bool PluginChannel::OnMessageReceived(const IPC::Message& msg) {
    185   if (log_messages_) {
    186     VLOG(1) << "received message @" << &msg << " on channel @" << this
    187             << " with type " << msg.type();
    188   }
    189   return NPChannelBase::OnMessageReceived(msg);
    190 }
    191 
    192 void PluginChannel::OnChannelError() {
    193   NPChannelBase::OnChannelError();
    194   CleanUp();
    195 }
    196 
    197 int PluginChannel::GenerateRouteID() {
    198   static int last_id = 0;
    199   return ++last_id;
    200 }
    201 
    202 base::WaitableEvent* PluginChannel::GetModalDialogEvent(int render_view_id) {
    203   return filter_->GetModalDialogEvent(render_view_id);
    204 }
    205 
    206 PluginChannel::~PluginChannel() {
    207   base::MessageLoop::current()->PostDelayedTask(
    208       FROM_HERE,
    209       base::Bind(&PluginReleaseCallback),
    210       base::TimeDelta::FromMinutes(kPluginReleaseTimeMinutes));
    211 }
    212 
    213 void PluginChannel::CleanUp() {
    214   // We need to clean up the stubs so that they call NPPDestroy.  This will
    215   // also lead to them releasing their reference on this object so that it can
    216   // be deleted.
    217   for (size_t i = 0; i < plugin_stubs_.size(); ++i)
    218     RemoveRoute(plugin_stubs_[i]->instance_id());
    219 
    220   // Need to addref this object temporarily because otherwise removing the last
    221   // stub will cause the destructor of this object to be called, however at
    222   // that point plugin_stubs_ will have one element and its destructor will be
    223   // called twice.
    224   scoped_refptr<PluginChannel> me(this);
    225 
    226   plugin_stubs_.clear();
    227 }
    228 
    229 bool PluginChannel::Init(base::MessageLoopProxy* ipc_message_loop,
    230                          bool create_pipe_now,
    231                          base::WaitableEvent* shutdown_event) {
    232   if (!NPChannelBase::Init(ipc_message_loop, create_pipe_now, shutdown_event))
    233     return false;
    234 
    235   channel_->AddFilter(filter_.get());
    236   return true;
    237 }
    238 
    239 PluginChannel::PluginChannel()
    240     : renderer_id_(-1),
    241       in_send_(0),
    242       incognito_(false),
    243       filter_(new MessageFilter()),
    244       npp_(new struct _NPP) {
    245   set_send_unblocking_only_during_unblock_dispatch();
    246   ChildProcess::current()->AddRefProcess();
    247   const CommandLine* command_line = CommandLine::ForCurrentProcess();
    248   log_messages_ = command_line->HasSwitch(switches::kLogPluginMessages);
    249 
    250   // Register |npp_| as the default owner for any object we receive via IPC,
    251   // and register it with WebBindings as a valid owner.
    252   SetDefaultNPObjectOwner(npp_.get());
    253   WebBindings::registerObjectOwner(npp_.get());
    254 }
    255 
    256 bool PluginChannel::OnControlMessageReceived(const IPC::Message& msg) {
    257   bool handled = true;
    258   IPC_BEGIN_MESSAGE_MAP(PluginChannel, msg)
    259     IPC_MESSAGE_HANDLER(PluginMsg_CreateInstance, OnCreateInstance)
    260     IPC_MESSAGE_HANDLER_DELAY_REPLY(PluginMsg_DestroyInstance,
    261                                     OnDestroyInstance)
    262     IPC_MESSAGE_HANDLER(PluginMsg_GenerateRouteID, OnGenerateRouteID)
    263     IPC_MESSAGE_HANDLER(PluginProcessMsg_ClearSiteData, OnClearSiteData)
    264     IPC_MESSAGE_UNHANDLED(handled = false)
    265   IPC_END_MESSAGE_MAP()
    266   DCHECK(handled);
    267   return handled;
    268 }
    269 
    270 void PluginChannel::OnCreateInstance(const std::string& mime_type,
    271                                      int* instance_id) {
    272   *instance_id = GenerateRouteID();
    273   scoped_refptr<WebPluginDelegateStub> stub(new WebPluginDelegateStub(
    274       mime_type, *instance_id, this));
    275   AddRoute(*instance_id, stub.get(), NULL);
    276   plugin_stubs_.push_back(stub);
    277 }
    278 
    279 void PluginChannel::OnDestroyInstance(int instance_id,
    280                                       IPC::Message* reply_msg) {
    281   for (size_t i = 0; i < plugin_stubs_.size(); ++i) {
    282     if (plugin_stubs_[i]->instance_id() == instance_id) {
    283       scoped_refptr<MessageFilter> filter(filter_);
    284       int render_view_id =
    285           plugin_stubs_[i]->webplugin()->host_render_view_routing_id();
    286       plugin_stubs_.erase(plugin_stubs_.begin() + i);
    287       Send(reply_msg);
    288       RemoveRoute(instance_id);
    289       // NOTE: *this* might be deleted as a result of calling RemoveRoute.
    290       // Don't release the modal dialog event right away, but do it after the
    291       // stack unwinds since the plugin can be destroyed later if it's in use
    292       // right now.
    293       base::MessageLoop::current()->PostNonNestableTask(
    294           FROM_HERE,
    295           base::Bind(&MessageFilter::ReleaseModalDialogEvent,
    296                      filter.get(),
    297                      render_view_id));
    298       return;
    299     }
    300   }
    301 
    302   NOTREACHED() << "Couldn't find WebPluginDelegateStub to destroy";
    303 }
    304 
    305 void PluginChannel::OnGenerateRouteID(int* route_id) {
    306   *route_id = GenerateRouteID();
    307 }
    308 
    309 void PluginChannel::OnClearSiteData(const std::string& site,
    310                                     uint64 flags,
    311                                     uint64 max_age) {
    312   bool success = false;
    313   CommandLine* command_line = CommandLine::ForCurrentProcess();
    314   base::FilePath path = command_line->GetSwitchValuePath(switches::kPluginPath);
    315   scoped_refptr<PluginLib> plugin_lib(PluginLib::CreatePluginLib(path));
    316   if (plugin_lib.get()) {
    317     NPError err = plugin_lib->NP_Initialize();
    318     if (err == NPERR_NO_ERROR) {
    319       const char* site_str = site.empty() ? NULL : site.c_str();
    320       err = plugin_lib->NP_ClearSiteData(site_str, flags, max_age);
    321       std::string site_name =
    322           site.empty() ? "NULL"
    323                        : base::StringPrintf("\"%s\"", site_str);
    324       VLOG(1) << "NPP_ClearSiteData(" << site_name << ", " << flags << ", "
    325               << max_age << ") returned " << err;
    326       success = (err == NPERR_NO_ERROR);
    327     }
    328   }
    329   Send(new PluginProcessHostMsg_ClearSiteDataResult(success));
    330 }
    331 
    332 }  // namespace content
    333