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