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