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