plugin_dispatcher.cc revision 9ab5563a3196760eb381d102cbb2bc0f7abc6a50
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 "ppapi/proxy/plugin_dispatcher.h"
6
7#include <map>
8
9#include "base/compiler_specific.h"
10#include "base/debug/trace_event.h"
11#include "base/logging.h"
12#include "base/message_loop/message_loop.h"
13#include "ipc/ipc_message.h"
14#include "ipc/ipc_sync_channel.h"
15#include "ipc/ipc_sync_message_filter.h"
16#include "ppapi/c/pp_errors.h"
17#include "ppapi/c/ppp_instance.h"
18#include "ppapi/proxy/flash_clipboard_resource.h"
19#include "ppapi/proxy/flash_file_resource.h"
20#include "ppapi/proxy/flash_resource.h"
21#include "ppapi/proxy/gamepad_resource.h"
22#include "ppapi/proxy/interface_list.h"
23#include "ppapi/proxy/interface_proxy.h"
24#include "ppapi/proxy/plugin_globals.h"
25#include "ppapi/proxy/plugin_message_filter.h"
26#include "ppapi/proxy/plugin_resource_tracker.h"
27#include "ppapi/proxy/plugin_var_serialization_rules.h"
28#include "ppapi/proxy/ppapi_messages.h"
29#include "ppapi/proxy/ppb_instance_proxy.h"
30#include "ppapi/proxy/ppp_class_proxy.h"
31#include "ppapi/proxy/resource_creation_proxy.h"
32#include "ppapi/proxy/resource_message_params.h"
33#include "ppapi/shared_impl/ppapi_globals.h"
34#include "ppapi/shared_impl/proxy_lock.h"
35#include "ppapi/shared_impl/resource.h"
36
37#if defined(OS_POSIX) && !defined(OS_NACL)
38#include "base/posix/eintr_wrapper.h"
39#include "ipc/ipc_channel_posix.h"
40#endif
41
42namespace ppapi {
43namespace proxy {
44
45namespace {
46
47typedef std::map<PP_Instance, PluginDispatcher*> InstanceToDispatcherMap;
48InstanceToDispatcherMap* g_instance_to_dispatcher = NULL;
49
50typedef std::set<PluginDispatcher*> DispatcherSet;
51DispatcherSet* g_live_dispatchers = NULL;
52
53}  // namespace
54
55InstanceData::InstanceData()
56    : is_request_surrounding_text_pending(false),
57      should_do_request_surrounding_text(false) {
58}
59
60InstanceData::~InstanceData() {
61  // Run any pending mouse lock callback to prevent leaks.
62  if (mouse_lock_callback.get())
63    mouse_lock_callback->Abort();
64}
65
66PluginDispatcher::PluginDispatcher(PP_GetInterface_Func get_interface,
67                                   const PpapiPermissions& permissions,
68                                   bool incognito)
69    : Dispatcher(get_interface, permissions),
70      plugin_delegate_(NULL),
71      received_preferences_(false),
72      plugin_dispatcher_id_(0),
73      incognito_(incognito) {
74  SetSerializationRules(new PluginVarSerializationRules(AsWeakPtr()));
75
76  if (!g_live_dispatchers)
77    g_live_dispatchers = new DispatcherSet;
78  g_live_dispatchers->insert(this);
79}
80
81PluginDispatcher::~PluginDispatcher() {
82  PluginGlobals::Get()->plugin_var_tracker()->DidDeleteDispatcher(this);
83
84  if (plugin_delegate_)
85    plugin_delegate_->Unregister(plugin_dispatcher_id_);
86
87  g_live_dispatchers->erase(this);
88  if (g_live_dispatchers->empty()) {
89    delete g_live_dispatchers;
90    g_live_dispatchers = NULL;
91  }
92}
93
94// static
95PluginDispatcher* PluginDispatcher::GetForInstance(PP_Instance instance) {
96  if (!g_instance_to_dispatcher)
97    return NULL;
98  InstanceToDispatcherMap::iterator found = g_instance_to_dispatcher->find(
99      instance);
100  if (found == g_instance_to_dispatcher->end())
101    return NULL;
102  return found->second;
103}
104
105// static
106PluginDispatcher* PluginDispatcher::GetForResource(const Resource* resource) {
107  return GetForInstance(resource->pp_instance());
108}
109
110// static
111const void* PluginDispatcher::GetBrowserInterface(const char* interface_name) {
112  if (!interface_name) {
113    DLOG(WARNING) << "|interface_name| is null. Did you forget to add "
114        "the |interface_name()| template function to the interface's C++ "
115        "wrapper?";
116    return NULL;
117  }
118
119  return InterfaceList::GetInstance()->GetInterfaceForPPB(interface_name);
120}
121
122// static
123void PluginDispatcher::LogWithSource(PP_Instance instance,
124                                     PP_LogLevel level,
125                                     const std::string& source,
126                                     const std::string& value) {
127  if (!g_live_dispatchers || !g_instance_to_dispatcher)
128    return;
129
130  if (instance) {
131    InstanceToDispatcherMap::iterator found =
132        g_instance_to_dispatcher->find(instance);
133    if (found != g_instance_to_dispatcher->end()) {
134      // Send just to this specific dispatcher.
135      found->second->Send(new PpapiHostMsg_LogWithSource(
136          instance, static_cast<int>(level), source, value));
137      return;
138    }
139  }
140
141  // Instance 0 or invalid, send to all dispatchers.
142  for (DispatcherSet::iterator i = g_live_dispatchers->begin();
143       i != g_live_dispatchers->end(); ++i) {
144    (*i)->Send(new PpapiHostMsg_LogWithSource(
145        instance, static_cast<int>(level), source, value));
146  }
147}
148
149const void* PluginDispatcher::GetPluginInterface(
150    const std::string& interface_name) {
151  InterfaceMap::iterator found = plugin_interfaces_.find(interface_name);
152  if (found == plugin_interfaces_.end()) {
153    const void* ret = local_get_interface()(interface_name.c_str());
154    plugin_interfaces_.insert(std::make_pair(interface_name, ret));
155    return ret;
156  }
157  return found->second;
158}
159
160bool PluginDispatcher::InitPluginWithChannel(
161    PluginDelegate* delegate,
162    base::ProcessId peer_pid,
163    const IPC::ChannelHandle& channel_handle,
164    bool is_client) {
165  if (!Dispatcher::InitWithChannel(delegate, peer_pid, channel_handle,
166                                   is_client))
167    return false;
168  plugin_delegate_ = delegate;
169  plugin_dispatcher_id_ = plugin_delegate_->Register(this);
170
171  sync_filter_ = new IPC::SyncMessageFilter(delegate->GetShutdownEvent());
172  channel()->AddFilter(sync_filter_.get());
173
174  // The message filter will intercept and process certain messages directly
175  // on the I/O thread.
176  channel()->AddFilter(
177      new PluginMessageFilter(delegate->GetGloballySeenInstanceIDSet()));
178  return true;
179}
180
181bool PluginDispatcher::IsPlugin() const {
182  return true;
183}
184
185bool PluginDispatcher::SendMessage(IPC::Message* msg) {
186  // Currently we need to choose between two different mechanisms for sending.
187  // On the main thread we use the regular dispatch Send() method, on another
188  // thread we use SyncMessageFilter.
189  if (PpapiGlobals::Get()->GetMainThreadMessageLoop()->BelongsToCurrentThread())
190    return Dispatcher::Send(msg);
191  return sync_filter_->Send(msg);
192}
193
194bool PluginDispatcher::Send(IPC::Message* msg) {
195  TRACE_EVENT2("ppapi proxy", "PluginDispatcher::Send",
196               "Class", IPC_MESSAGE_ID_CLASS(msg->type()),
197               "Line", IPC_MESSAGE_ID_LINE(msg->type()));
198  // We always want plugin->renderer messages to arrive in-order. If some sync
199  // and some async messages are sent in response to a synchronous
200  // renderer->plugin call, the sync reply will be processed before the async
201  // reply, and everything will be confused.
202  //
203  // Allowing all async messages to unblock the renderer means more reentrancy
204  // there but gives correct ordering.
205  //
206  // We don't want reply messages to unblock however, as they will potentially
207  // end up on the wrong queue - see crbug.com/122443
208  if (!msg->is_reply())
209    msg->set_unblock(true);
210  if (msg->is_sync()) {
211    // Synchronous messages might be re-entrant, so we need to drop the lock.
212    ProxyAutoUnlock unlock;
213    return SendMessage(msg);
214  }
215  return SendMessage(msg);
216}
217
218bool PluginDispatcher::OnMessageReceived(const IPC::Message& msg) {
219  // We need to grab the proxy lock to ensure that we don't collide with the
220  // plugin making pepper calls on a different thread.
221  ProxyAutoLock lock;
222  TRACE_EVENT2("ppapi proxy", "PluginDispatcher::OnMessageReceived",
223               "Class", IPC_MESSAGE_ID_CLASS(msg.type()),
224               "Line", IPC_MESSAGE_ID_LINE(msg.type()));
225
226  if (msg.routing_id() == MSG_ROUTING_CONTROL) {
227    // Handle some plugin-specific control messages.
228    bool handled = true;
229    IPC_BEGIN_MESSAGE_MAP(PluginDispatcher, msg)
230      IPC_MESSAGE_HANDLER(PpapiPluginMsg_ResourceReply, OnMsgResourceReply)
231      IPC_MESSAGE_HANDLER(PpapiMsg_SupportsInterface, OnMsgSupportsInterface)
232      IPC_MESSAGE_HANDLER(PpapiMsg_SetPreferences, OnMsgSetPreferences)
233      IPC_MESSAGE_UNHANDLED(handled = false);
234    IPC_END_MESSAGE_MAP()
235    if (handled)
236      return true;
237  }
238  return Dispatcher::OnMessageReceived(msg);
239}
240
241void PluginDispatcher::OnChannelError() {
242  Dispatcher::OnChannelError();
243
244  // The renderer has crashed or exited. This channel and all instances
245  // associated with it are no longer valid.
246  ForceFreeAllInstances();
247  // TODO(brettw) free resources too!
248  delete this;
249}
250
251void PluginDispatcher::DidCreateInstance(PP_Instance instance) {
252  if (!g_instance_to_dispatcher)
253    g_instance_to_dispatcher = new InstanceToDispatcherMap;
254  (*g_instance_to_dispatcher)[instance] = this;
255
256  instance_map_[instance] = InstanceData();
257}
258
259void PluginDispatcher::DidDestroyInstance(PP_Instance instance) {
260  InstanceDataMap::iterator it = instance_map_.find(instance);
261  if (it != instance_map_.end())
262    instance_map_.erase(it);
263
264  if (g_instance_to_dispatcher) {
265    InstanceToDispatcherMap::iterator found = g_instance_to_dispatcher->find(
266        instance);
267    if (found != g_instance_to_dispatcher->end()) {
268      DCHECK(found->second == this);
269      g_instance_to_dispatcher->erase(found);
270    } else {
271      NOTREACHED();
272    }
273  }
274}
275
276InstanceData* PluginDispatcher::GetInstanceData(PP_Instance instance) {
277  InstanceDataMap::iterator it = instance_map_.find(instance);
278  return (it == instance_map_.end()) ? NULL : &it->second;
279}
280
281thunk::PPB_Instance_API* PluginDispatcher::GetInstanceAPI() {
282  return static_cast<PPB_Instance_Proxy*>(
283      GetInterfaceProxy(API_ID_PPB_INSTANCE));
284}
285
286thunk::ResourceCreationAPI* PluginDispatcher::GetResourceCreationAPI() {
287  return static_cast<ResourceCreationProxy*>(
288      GetInterfaceProxy(API_ID_RESOURCE_CREATION));
289}
290
291// static
292void PluginDispatcher::DispatchResourceReply(
293    const ppapi::proxy::ResourceMessageReplyParams& reply_params,
294    const IPC::Message& nested_msg) {
295  // We need to grab the proxy lock to ensure that we don't collide with the
296  // plugin making pepper calls on a different thread.
297  ProxyAutoLock lock;
298  LockedDispatchResourceReply(reply_params, nested_msg);
299}
300
301void PluginDispatcher::ForceFreeAllInstances() {
302  if (!g_instance_to_dispatcher)
303    return;
304
305  // Iterating will remove each item from the map, so we need to make a copy
306  // to avoid things changing out from under is.
307  InstanceToDispatcherMap temp_map = *g_instance_to_dispatcher;
308  for (InstanceToDispatcherMap::iterator i = temp_map.begin();
309       i != temp_map.end(); ++i) {
310    if (i->second == this) {
311      // Synthesize an "instance destroyed" message, this will notify the
312      // plugin and also remove it from our list of tracked plugins.
313      PpapiMsg_PPPInstance_DidDestroy msg(API_ID_PPP_INSTANCE, i->first);
314      OnMessageReceived(msg);
315    }
316  }
317}
318
319void PluginDispatcher::OnMsgResourceReply(
320    const ppapi::proxy::ResourceMessageReplyParams& reply_params,
321    const IPC::Message& nested_msg) {
322  LockedDispatchResourceReply(reply_params, nested_msg);
323}
324
325void PluginDispatcher::OnMsgSupportsInterface(
326    const std::string& interface_name,
327    bool* result) {
328  *result = !!GetPluginInterface(interface_name);
329
330  // Do fallback for PPP_Instance. This is a hack here and if we have more
331  // cases like this it should be generalized. The PPP_Instance proxy always
332  // proxies the 1.1 interface, and then does fallback to 1.0 inside the
333  // plugin process (see PPP_Instance_Proxy). So here we return true for
334  // supporting the 1.1 interface if either 1.1 or 1.0 is supported.
335  if (!*result && interface_name == PPP_INSTANCE_INTERFACE)
336    *result = !!GetPluginInterface(PPP_INSTANCE_INTERFACE_1_0);
337}
338
339void PluginDispatcher::OnMsgSetPreferences(const Preferences& prefs) {
340  // The renderer may send us preferences more than once (currently this
341  // happens every time a new plugin instance is created). Since we don't have
342  // a way to signal to the plugin that the preferences have changed, changing
343  // the default fonts and such in the middle of a running plugin could be
344  // confusing to it. As a result, we never allow the preferences to be changed
345  // once they're set. The user will have to restart to get new font prefs
346  // propogated to plugins.
347  if (!received_preferences_) {
348    received_preferences_ = true;
349    preferences_ = prefs;
350  }
351}
352
353// static
354void PluginDispatcher::LockedDispatchResourceReply(
355    const ppapi::proxy::ResourceMessageReplyParams& reply_params,
356    const IPC::Message& nested_msg) {
357  Resource* resource = PpapiGlobals::Get()->GetResourceTracker()->GetResource(
358      reply_params.pp_resource());
359  if (!resource) {
360    DLOG_IF(INFO, reply_params.sequence() != 0)
361        << "Pepper resource reply message received but the resource doesn't "
362           "exist (probably has been destroyed).";
363    return;
364  }
365  resource->OnReplyReceived(reply_params, nested_msg);
366}
367
368}  // namespace proxy
369}  // namespace ppapi
370