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.
5 #include "content/plugin/plugin_channel.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"
27 #include "ipc/ipc_channel_posix.h"
30 using blink::WebBindings
;
36 // How long we wait before releasing the plugin process.
37 const int kPluginReleaseTimeMinutes
= 5;
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
{
46 MessageFilter() : sender_(NULL
) { }
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
)) {
55 return modal_dialog_event_map_
[render_view_id
].event
;
58 // Decrement the ref count associated with the modal dialog event for the
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
)) {
67 if (--(modal_dialog_event_map_
[render_view_id
].refcount
))
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
);
76 bool Send(IPC::Message
* message
) {
77 // Need this function for the IPC_MESSAGE_HANDLER_DELAY_REPLY macro.
78 return sender_
->Send(message
);
81 // IPC::MessageFilter:
82 void OnFilterAdded(IPC::Sender
* sender
) override
{ sender_
= sender
; }
84 bool OnMessageReceived(const IPC::Message
& message
) override
{
85 IPC_BEGIN_MESSAGE_MAP(PluginChannel::MessageFilter
, message
)
86 IPC_MESSAGE_HANDLER_DELAY_REPLY(PluginMsg_Init
, OnInit
)
87 IPC_MESSAGE_HANDLER(PluginMsg_SignalModalDialogEvent
,
88 OnSignalModalDialogEvent
)
89 IPC_MESSAGE_HANDLER(PluginMsg_ResetModalDialogEvent
,
90 OnResetModalDialogEvent
)
92 return message
.type() == PluginMsg_SignalModalDialogEvent::ID
||
93 message
.type() == PluginMsg_ResetModalDialogEvent::ID
;
97 ~MessageFilter() override
{
98 // Clean up in case of renderer crash.
99 for (ModalDialogEventMap::iterator i
= modal_dialog_event_map_
.begin();
100 i
!= modal_dialog_event_map_
.end(); ++i
) {
101 delete i
->second
.event
;
106 void OnInit(const PluginMsg_Init_Params
& params
, IPC::Message
* reply_msg
) {
107 base::AutoLock
auto_lock(modal_dialog_event_map_lock_
);
108 if (modal_dialog_event_map_
.count(params
.host_render_view_routing_id
)) {
109 modal_dialog_event_map_
[params
.host_render_view_routing_id
].refcount
++;
113 WaitableEventWrapper wrapper
;
114 wrapper
.event
= new base::WaitableEvent(true, false);
115 wrapper
.refcount
= 1;
116 modal_dialog_event_map_
[params
.host_render_view_routing_id
] = wrapper
;
119 void OnSignalModalDialogEvent(int render_view_id
) {
120 base::AutoLock
auto_lock(modal_dialog_event_map_lock_
);
121 if (modal_dialog_event_map_
.count(render_view_id
))
122 modal_dialog_event_map_
[render_view_id
].event
->Signal();
125 void OnResetModalDialogEvent(int render_view_id
) {
126 base::AutoLock
auto_lock(modal_dialog_event_map_lock_
);
127 if (modal_dialog_event_map_
.count(render_view_id
))
128 modal_dialog_event_map_
[render_view_id
].event
->Reset();
131 struct WaitableEventWrapper
{
132 base::WaitableEvent
* event
;
133 int refcount
; // There could be multiple plugin instances per tab.
135 typedef std::map
<int, WaitableEventWrapper
> ModalDialogEventMap
;
136 ModalDialogEventMap modal_dialog_event_map_
;
137 base::Lock modal_dialog_event_map_lock_
;
139 IPC::Sender
* sender_
;
142 PluginChannel
* PluginChannel::GetPluginChannel(
144 base::SingleThreadTaskRunner
* ipc_task_runner
,
145 IPC::AttachmentBroker
* broker
) {
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
);
150 PluginChannel
* channel
=
151 static_cast<PluginChannel
*>(NPChannelBase::GetChannel(
152 channel_key
, IPC::Channel::MODE_SERVER
, ClassFactory
, ipc_task_runner
,
153 false, ChildProcess::current()->GetShutDownEvent(), broker
));
156 channel
->renderer_id_
= renderer_id
;
162 void PluginChannel::NotifyRenderersOfPendingShutdown() {
163 Broadcast(new PluginHostMsg_PluginShuttingDown());
166 bool PluginChannel::Send(IPC::Message
* msg
) {
169 VLOG(1) << "sending message @" << msg
<< " on channel @" << this
170 << " with type " << msg
->type();
172 bool result
= NPChannelBase::Send(msg
);
177 bool PluginChannel::OnMessageReceived(const IPC::Message
& msg
) {
179 VLOG(1) << "received message @" << &msg
<< " on channel @" << this
180 << " with type " << msg
.type();
182 return NPChannelBase::OnMessageReceived(msg
);
185 void PluginChannel::OnChannelError() {
186 NPChannelBase::OnChannelError();
190 int PluginChannel::GenerateRouteID() {
191 static int last_id
= 0;
195 base::WaitableEvent
* PluginChannel::GetModalDialogEvent(int render_view_id
) {
196 return filter_
->GetModalDialogEvent(render_view_id
);
199 PluginChannel::~PluginChannel() {
200 PluginThread::current()->Send(new PluginProcessHostMsg_ChannelDestroyed(
202 process_ref_
.ReleaseWithDelay(
203 base::TimeDelta::FromMinutes(kPluginReleaseTimeMinutes
));
206 void PluginChannel::CleanUp() {
207 // We need to clean up the stubs so that they call NPPDestroy. This will
208 // also lead to them releasing their reference on this object so that it can
210 for (size_t i
= 0; i
< plugin_stubs_
.size(); ++i
)
211 RemoveRoute(plugin_stubs_
[i
]->instance_id());
213 // Need to addref this object temporarily because otherwise removing the last
214 // stub will cause the destructor of this object to be called, however at
215 // that point plugin_stubs_ will have one element and its destructor will be
217 scoped_refptr
<PluginChannel
> me(this);
219 while (!plugin_stubs_
.empty()) {
220 // Separate vector::erase and ~WebPluginDelegateStub.
221 // See https://code.google.com/p/chromium/issues/detail?id=314088
222 scoped_refptr
<WebPluginDelegateStub
> stub
= plugin_stubs_
[0];
223 plugin_stubs_
.erase(plugin_stubs_
.begin());
227 bool PluginChannel::Init(base::SingleThreadTaskRunner
* ipc_task_runner
,
228 bool create_pipe_now
,
229 base::WaitableEvent
* shutdown_event
,
230 IPC::AttachmentBroker
* broker
) {
231 if (!NPChannelBase::Init(ipc_task_runner
, create_pipe_now
, shutdown_event
,
236 channel_
->AddFilter(filter_
.get());
240 PluginChannel::PluginChannel()
244 filter_(new MessageFilter()),
245 npp_(new struct _NPP
) {
246 set_send_unblocking_only_during_unblock_dispatch();
247 const base::CommandLine
* command_line
=
248 base::CommandLine::ForCurrentProcess();
249 log_messages_
= command_line
->HasSwitch(switches::kLogPluginMessages
);
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());
257 bool PluginChannel::OnControlMessageReceived(const IPC::Message
& msg
) {
259 IPC_BEGIN_MESSAGE_MAP(PluginChannel
, msg
)
260 IPC_MESSAGE_HANDLER(PluginMsg_CreateInstance
, OnCreateInstance
)
261 IPC_MESSAGE_HANDLER_DELAY_REPLY(PluginMsg_DestroyInstance
,
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()
272 void PluginChannel::OnCreateInstance(const std::string
& mime_type
,
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
);
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_
);
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
);
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
300 base::MessageLoop::current()->PostNonNestableTask(
302 base::Bind(&MessageFilter::ReleaseModalDialogEvent
,
309 NOTREACHED() << "Couldn't find WebPluginDelegateStub to destroy";
312 void PluginChannel::OnGenerateRouteID(int* route_id
) {
313 *route_id
= GenerateRouteID();
316 void PluginChannel::OnClearSiteData(const std::string
& site
,
319 bool success
= false;
320 base::CommandLine
* command_line
= base::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
);
336 Send(new PluginProcessHostMsg_ClearSiteDataResult(success
));
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() ==
343 plugin_stubs_
[i
]->delegate()->instance()->CloseStreams();
348 } // namespace content