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