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
30using blink::WebBindings;
31
32namespace content {
33
34namespace {
35
36// How long we wait before releasing the plugin process.
37const 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.
44class 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
144PluginChannel* 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
166void PluginChannel::NotifyRenderersOfPendingShutdown() {
167  Broadcast(new PluginHostMsg_PluginShuttingDown());
168}
169
170bool 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
181bool 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
189void PluginChannel::OnChannelError() {
190  NPChannelBase::OnChannelError();
191  CleanUp();
192}
193
194int PluginChannel::GenerateRouteID() {
195  static int last_id = 0;
196  return ++last_id;
197}
198
199base::WaitableEvent* PluginChannel::GetModalDialogEvent(int render_view_id) {
200  return filter_->GetModalDialogEvent(render_view_id);
201}
202
203PluginChannel::~PluginChannel() {
204  PluginThread::current()->Send(new PluginProcessHostMsg_ChannelDestroyed(
205      renderer_id_));
206  process_ref_.ReleaseWithDelay(
207      base::TimeDelta::FromMinutes(kPluginReleaseTimeMinutes));
208}
209
210void 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
231bool 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
241PluginChannel::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
257bool 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
272void 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
281void 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
312void PluginChannel::OnGenerateRouteID(int* route_id) {
313  *route_id = GenerateRouteID();
314}
315
316void 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
339void 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