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_var_tracker.h"
6
7#include "base/memory/ref_counted.h"
8#include "base/memory/singleton.h"
9#include "ipc/ipc_message.h"
10#include "ppapi/c/dev/ppp_class_deprecated.h"
11#include "ppapi/c/ppb_var.h"
12#include "ppapi/proxy/file_system_resource.h"
13#include "ppapi/proxy/media_stream_audio_track_resource.h"
14#include "ppapi/proxy/media_stream_video_track_resource.h"
15#include "ppapi/proxy/plugin_array_buffer_var.h"
16#include "ppapi/proxy/plugin_dispatcher.h"
17#include "ppapi/proxy/plugin_globals.h"
18#include "ppapi/proxy/plugin_resource_var.h"
19#include "ppapi/proxy/ppapi_messages.h"
20#include "ppapi/proxy/proxy_object_var.h"
21#include "ppapi/shared_impl/api_id.h"
22#include "ppapi/shared_impl/ppapi_globals.h"
23#include "ppapi/shared_impl/proxy_lock.h"
24#include "ppapi/shared_impl/resource_tracker.h"
25#include "ppapi/shared_impl/var.h"
26
27namespace ppapi {
28namespace proxy {
29
30namespace {
31
32Connection GetConnectionForInstance(PP_Instance instance) {
33  PluginDispatcher* dispatcher = PluginDispatcher::GetForInstance(instance);
34  DCHECK(dispatcher);
35  return Connection(PluginGlobals::Get()->GetBrowserSender(), dispatcher);
36}
37
38}  // namespace
39
40PluginVarTracker::HostVar::HostVar(PluginDispatcher* d, int32 i)
41    : dispatcher(d),
42      host_object_id(i) {
43}
44
45bool PluginVarTracker::HostVar::operator<(const HostVar& other) const {
46  if (dispatcher < other.dispatcher)
47    return true;
48  if (other.dispatcher < dispatcher)
49    return false;
50  return host_object_id < other.host_object_id;
51}
52
53PluginVarTracker::PluginVarTracker() : VarTracker(THREAD_SAFE) {
54}
55
56PluginVarTracker::~PluginVarTracker() {
57}
58
59PP_Var PluginVarTracker::ReceiveObjectPassRef(const PP_Var& host_var,
60                                              PluginDispatcher* dispatcher) {
61  CheckThreadingPreconditions();
62  DCHECK(host_var.type == PP_VARTYPE_OBJECT);
63
64  // Get the object.
65  scoped_refptr<ProxyObjectVar> object(
66      FindOrMakePluginVarFromHostVar(host_var, dispatcher));
67
68  // Actually create the PP_Var, this will add all the tracking info but not
69  // adjust any refcounts.
70  PP_Var ret = GetOrCreateObjectVarID(object.get());
71
72  VarInfo& info = GetLiveVar(ret)->second;
73  if (info.ref_count > 0) {
74    // We already had a reference to it before. That means the renderer now has
75    // two references on our behalf. We want to transfer that extra reference
76    // to our list. This means we addref in the plugin, and release the extra
77    // one in the renderer.
78    SendReleaseObjectMsg(*object.get());
79  }
80  info.ref_count++;
81  return ret;
82}
83
84PP_Var PluginVarTracker::TrackObjectWithNoReference(
85    const PP_Var& host_var,
86    PluginDispatcher* dispatcher) {
87  CheckThreadingPreconditions();
88  DCHECK(host_var.type == PP_VARTYPE_OBJECT);
89
90  // Get the object.
91  scoped_refptr<ProxyObjectVar> object(
92      FindOrMakePluginVarFromHostVar(host_var, dispatcher));
93
94  // Actually create the PP_Var, this will add all the tracking info but not
95  // adjust any refcounts.
96  PP_Var ret = GetOrCreateObjectVarID(object.get());
97
98  VarInfo& info = GetLiveVar(ret)->second;
99  info.track_with_no_reference_count++;
100  return ret;
101}
102
103void PluginVarTracker::StopTrackingObjectWithNoReference(
104    const PP_Var& plugin_var) {
105  CheckThreadingPreconditions();
106  DCHECK(plugin_var.type == PP_VARTYPE_OBJECT);
107
108  VarMap::iterator found = GetLiveVar(plugin_var);
109  if (found == live_vars_.end()) {
110    NOTREACHED();
111    return;
112  }
113
114  DCHECK(found->second.track_with_no_reference_count > 0);
115  found->second.track_with_no_reference_count--;
116  DeleteObjectInfoIfNecessary(found);
117}
118
119PP_Var PluginVarTracker::GetHostObject(const PP_Var& plugin_object) const {
120  CheckThreadingPreconditions();
121  if (plugin_object.type != PP_VARTYPE_OBJECT) {
122    NOTREACHED();
123    return PP_MakeUndefined();
124  }
125
126  Var* var = GetVar(plugin_object);
127  ProxyObjectVar* object = var->AsProxyObjectVar();
128  if (!object) {
129    NOTREACHED();
130    return PP_MakeUndefined();
131  }
132
133  // Make a var with the host ID.
134  PP_Var ret = { PP_VARTYPE_OBJECT };
135  ret.value.as_id = object->host_var_id();
136  return ret;
137}
138
139PluginDispatcher* PluginVarTracker::DispatcherForPluginObject(
140    const PP_Var& plugin_object) const {
141  CheckThreadingPreconditions();
142  if (plugin_object.type != PP_VARTYPE_OBJECT)
143    return NULL;
144
145  VarMap::const_iterator found = GetLiveVar(plugin_object);
146  if (found == live_vars_.end())
147    return NULL;
148
149  ProxyObjectVar* object = found->second.var->AsProxyObjectVar();
150  if (!object)
151    return NULL;
152  return object->dispatcher();
153}
154
155void PluginVarTracker::ReleaseHostObject(PluginDispatcher* dispatcher,
156                                         const PP_Var& host_object) {
157  CheckThreadingPreconditions();
158  DCHECK(host_object.type == PP_VARTYPE_OBJECT);
159
160  // Convert the host object to a normal var valid in the plugin.
161  HostVarToPluginVarMap::iterator found = host_var_to_plugin_var_.find(
162      HostVar(dispatcher, static_cast<int32>(host_object.value.as_id)));
163  if (found == host_var_to_plugin_var_.end()) {
164    NOTREACHED();
165    return;
166  }
167
168  // Now just release the object given the plugin var ID.
169  ReleaseVar(found->second);
170}
171
172PP_Var PluginVarTracker::MakeResourcePPVarFromMessage(
173    PP_Instance instance,
174    const IPC::Message& creation_message,
175    int pending_renderer_id,
176    int pending_browser_id) {
177  switch (creation_message.type()) {
178    case PpapiPluginMsg_FileSystem_CreateFromPendingHost::ID: {
179      DCHECK(pending_renderer_id);
180      DCHECK(pending_browser_id);
181      PP_FileSystemType file_system_type;
182      if (!UnpackMessage<PpapiPluginMsg_FileSystem_CreateFromPendingHost>(
183               creation_message, &file_system_type)) {
184        NOTREACHED() << "Invalid message of type "
185                        "PpapiPluginMsg_FileSystem_CreateFromPendingHost";
186        return PP_MakeNull();
187      }
188      // Create a plugin-side resource and attach it to the host resource.
189      // Note: This only makes sense when the plugin is out of process (which
190      // should always be true when passing resource vars).
191      PP_Resource pp_resource =
192          (new FileSystemResource(GetConnectionForInstance(instance),
193                                  instance,
194                                  pending_renderer_id,
195                                  pending_browser_id,
196                                  file_system_type))->GetReference();
197      return MakeResourcePPVar(pp_resource);
198    }
199    case PpapiPluginMsg_MediaStreamAudioTrack_CreateFromPendingHost::ID: {
200      DCHECK(pending_renderer_id);
201      std::string track_id;
202      if (!UnpackMessage<
203              PpapiPluginMsg_MediaStreamAudioTrack_CreateFromPendingHost>(
204          creation_message, &track_id)) {
205        NOTREACHED() <<
206            "Invalid message of type "
207            "PpapiPluginMsg_MediaStreamAudioTrack_CreateFromPendingHost";
208        return PP_MakeNull();
209      }
210      PP_Resource pp_resource =
211          (new MediaStreamAudioTrackResource(GetConnectionForInstance(instance),
212                                             instance,
213                                             pending_renderer_id,
214                                             track_id))->GetReference();
215      return MakeResourcePPVar(pp_resource);
216    }
217    case PpapiPluginMsg_MediaStreamVideoTrack_CreateFromPendingHost::ID: {
218      DCHECK(pending_renderer_id);
219      std::string track_id;
220      if (!UnpackMessage<
221              PpapiPluginMsg_MediaStreamVideoTrack_CreateFromPendingHost>(
222          creation_message, &track_id)) {
223        NOTREACHED() <<
224            "Invalid message of type "
225            "PpapiPluginMsg_MediaStreamVideoTrack_CreateFromPendingHost";
226        return PP_MakeNull();
227      }
228      PP_Resource pp_resource =
229          (new MediaStreamVideoTrackResource(GetConnectionForInstance(instance),
230                                             instance,
231                                             pending_renderer_id,
232                                             track_id))->GetReference();
233      return MakeResourcePPVar(pp_resource);
234    }
235    default: {
236      NOTREACHED() << "Creation message has unexpected type "
237                   << creation_message.type();
238      return PP_MakeNull();
239    }
240  }
241}
242
243ResourceVar* PluginVarTracker::MakeResourceVar(PP_Resource pp_resource) {
244  // The resource 0 returns a null resource var.
245  if (!pp_resource)
246    return new PluginResourceVar();
247
248  ResourceTracker* resource_tracker = PpapiGlobals::Get()->GetResourceTracker();
249  ppapi::Resource* resource = resource_tracker->GetResource(pp_resource);
250  // A non-existant resource other than 0 returns NULL.
251  if (!resource)
252    return NULL;
253  return new PluginResourceVar(resource);
254}
255
256void PluginVarTracker::DidDeleteInstance(PP_Instance instance) {
257  // Calling the destructors on plugin objects may in turn release other
258  // objects which will mutate the map out from under us. So do a two-step
259  // process of identifying the ones to delete, and then delete them.
260  //
261  // See the comment above user_data_to_plugin_ in the header file. We assume
262  // there aren't that many objects so a brute-force search is reasonable.
263  std::vector<void*> user_data_to_delete;
264  for (UserDataToPluginImplementedVarMap::const_iterator i =
265           user_data_to_plugin_.begin();
266       i != user_data_to_plugin_.end();
267       ++i) {
268    if (i->second.instance == instance)
269      user_data_to_delete.push_back(i->first);
270  }
271
272  for (size_t i = 0; i < user_data_to_delete.size(); i++) {
273    UserDataToPluginImplementedVarMap::iterator found =
274        user_data_to_plugin_.find(user_data_to_delete[i]);
275    if (found == user_data_to_plugin_.end())
276      continue;  // Object removed from list while we were iterating.
277
278    if (!found->second.plugin_object_id) {
279      // This object is for the freed instance and the plugin is not holding
280      // any references to it. Deallocate immediately.
281      CallWhileUnlocked(found->second.ppp_class->Deallocate, found->first);
282      user_data_to_plugin_.erase(found);
283    } else {
284      // The plugin is holding refs to this object. We don't want to call
285      // Deallocate since the plugin may be depending on those refs to keep
286      // its data alive. To avoid crashes in this case, just clear out the
287      // instance to mark it and continue. When the plugin refs go to 0,
288      // we'll notice there is no instance and call Deallocate.
289      found->second.instance = 0;
290    }
291  }
292}
293
294void PluginVarTracker::DidDeleteDispatcher(PluginDispatcher* dispatcher) {
295  for (VarMap::iterator it = live_vars_.begin();
296       it != live_vars_.end();
297       ++it) {
298    if (it->second.var.get() == NULL)
299      continue;
300    ProxyObjectVar* object = it->second.var->AsProxyObjectVar();
301    if (object && object->dispatcher() == dispatcher)
302      object->clear_dispatcher();
303  }
304}
305
306ArrayBufferVar* PluginVarTracker::CreateArrayBuffer(uint32 size_in_bytes) {
307  return new PluginArrayBufferVar(size_in_bytes);
308}
309
310ArrayBufferVar* PluginVarTracker::CreateShmArrayBuffer(
311    uint32 size_in_bytes,
312    base::SharedMemoryHandle handle) {
313  return new PluginArrayBufferVar(size_in_bytes, handle);
314}
315
316void PluginVarTracker::PluginImplementedObjectCreated(
317    PP_Instance instance,
318    const PP_Var& created_var,
319    const PPP_Class_Deprecated* ppp_class,
320    void* ppp_class_data) {
321  PluginImplementedVar p;
322  p.ppp_class = ppp_class;
323  p.instance = instance;
324  p.plugin_object_id = created_var.value.as_id;
325  user_data_to_plugin_[ppp_class_data] = p;
326
327  // Link the user data to the object.
328  ProxyObjectVar* object = GetVar(created_var)->AsProxyObjectVar();
329  object->set_user_data(ppp_class_data);
330}
331
332void PluginVarTracker::PluginImplementedObjectDestroyed(void* user_data) {
333  UserDataToPluginImplementedVarMap::iterator found =
334      user_data_to_plugin_.find(user_data);
335  if (found == user_data_to_plugin_.end()) {
336    NOTREACHED();
337    return;
338  }
339  user_data_to_plugin_.erase(found);
340}
341
342bool PluginVarTracker::IsPluginImplementedObjectAlive(void* user_data) {
343  return user_data_to_plugin_.find(user_data) != user_data_to_plugin_.end();
344}
345
346bool PluginVarTracker::ValidatePluginObjectCall(
347    const PPP_Class_Deprecated* ppp_class,
348    void* user_data) {
349  UserDataToPluginImplementedVarMap::iterator found =
350      user_data_to_plugin_.find(user_data);
351  if (found == user_data_to_plugin_.end())
352    return false;
353  return found->second.ppp_class == ppp_class;
354}
355
356int32 PluginVarTracker::AddVarInternal(Var* var, AddVarRefMode mode) {
357  // Normal adding.
358  int32 new_id = VarTracker::AddVarInternal(var, mode);
359
360  // Need to add proxy objects to the host var map.
361  ProxyObjectVar* proxy_object = var->AsProxyObjectVar();
362  if (proxy_object) {
363    HostVar host_var(proxy_object->dispatcher(), proxy_object->host_var_id());
364    // TODO(teravest): Change to DCHECK when http://crbug.com/276347 is
365    // resolved.
366    CHECK(host_var_to_plugin_var_.find(host_var) ==
367          host_var_to_plugin_var_.end());  // Adding an object twice, use
368                                           // FindOrMakePluginVarFromHostVar.
369    host_var_to_plugin_var_[host_var] = new_id;
370  }
371  return new_id;
372}
373
374void PluginVarTracker::TrackedObjectGettingOneRef(VarMap::const_iterator iter) {
375  ProxyObjectVar* object = iter->second.var->AsProxyObjectVar();
376  if (!object) {
377    NOTREACHED();
378    return;
379  }
380
381  DCHECK(iter->second.ref_count == 0);
382
383  // Got an AddRef for an object we have no existing reference for.
384  // We need to tell the browser we've taken a ref. This comes up when the
385  // browser passes an object as an input param and holds a ref for us.
386  // This must be a sync message since otherwise the "addref" will actually
387  // occur after the return to the browser of the sync function that
388  // presumably sent the object.
389  SendAddRefObjectMsg(*object);
390}
391
392void PluginVarTracker::ObjectGettingZeroRef(VarMap::iterator iter) {
393  ProxyObjectVar* object = iter->second.var->AsProxyObjectVar();
394  if (!object) {
395    NOTREACHED();
396    return;
397  }
398
399  // Notify the host we're no longer holding our ref.
400  DCHECK(iter->second.ref_count == 0);
401  SendReleaseObjectMsg(*object);
402
403  UserDataToPluginImplementedVarMap::iterator found =
404      user_data_to_plugin_.find(object->user_data());
405  if (found != user_data_to_plugin_.end()) {
406    // This object is implemented in the plugin.
407    if (found->second.instance == 0) {
408      // Instance is destroyed. This means that we'll never get a Deallocate
409      // call from the renderer and we should do so now.
410      found->second.ppp_class->Deallocate(found->first);
411      user_data_to_plugin_.erase(found);
412    } else {
413      // The plugin is releasing its last reference to an object it implements.
414      // Clear the tracking data that links our "plugin implemented object" to
415      // the var. If the instance is destroyed and there is no ID, we know that
416      // we should just call Deallocate on the object data.
417      //
418      // See the plugin_object_id declaration for more info.
419      found->second.plugin_object_id = 0;
420    }
421  }
422
423  // This will optionally delete the info from live_vars_.
424  VarTracker::ObjectGettingZeroRef(iter);
425}
426
427bool PluginVarTracker::DeleteObjectInfoIfNecessary(VarMap::iterator iter) {
428  // Get the info before calling the base class's version of this function,
429  // which may delete the object.
430  ProxyObjectVar* object = iter->second.var->AsProxyObjectVar();
431  HostVar host_var(object->dispatcher(), object->host_var_id());
432
433  if (!VarTracker::DeleteObjectInfoIfNecessary(iter))
434    return false;
435
436  // Clean up the host var mapping.
437  DCHECK(host_var_to_plugin_var_.find(host_var) !=
438         host_var_to_plugin_var_.end());
439  host_var_to_plugin_var_.erase(host_var);
440  return true;
441}
442
443PP_Var PluginVarTracker::GetOrCreateObjectVarID(ProxyObjectVar* object) {
444  // We can't use object->GetPPVar() because we don't want to affect the
445  // refcount, so we have to add everything manually here.
446  int32 var_id = object->GetExistingVarID();
447  if (!var_id) {
448    var_id = AddVarInternal(object, ADD_VAR_CREATE_WITH_NO_REFERENCE);
449    object->AssignVarID(var_id);
450  }
451
452  PP_Var ret = { PP_VARTYPE_OBJECT };
453  ret.value.as_id = var_id;
454  return ret;
455}
456
457void PluginVarTracker::SendAddRefObjectMsg(
458    const ProxyObjectVar& proxy_object) {
459  if (proxy_object.dispatcher()) {
460    proxy_object.dispatcher()->Send(new PpapiHostMsg_PPBVar_AddRefObject(
461        API_ID_PPB_VAR_DEPRECATED, proxy_object.host_var_id()));
462  }
463}
464
465void PluginVarTracker::SendReleaseObjectMsg(
466    const ProxyObjectVar& proxy_object) {
467  if (proxy_object.dispatcher()) {
468    proxy_object.dispatcher()->Send(new PpapiHostMsg_PPBVar_ReleaseObject(
469        API_ID_PPB_VAR_DEPRECATED, proxy_object.host_var_id()));
470  }
471}
472
473scoped_refptr<ProxyObjectVar> PluginVarTracker::FindOrMakePluginVarFromHostVar(
474    const PP_Var& var,
475    PluginDispatcher* dispatcher) {
476  DCHECK(var.type == PP_VARTYPE_OBJECT);
477  HostVar host_var(dispatcher, var.value.as_id);
478
479  HostVarToPluginVarMap::iterator found =
480      host_var_to_plugin_var_.find(host_var);
481  if (found == host_var_to_plugin_var_.end()) {
482    // Create a new object.
483    return scoped_refptr<ProxyObjectVar>(
484        new ProxyObjectVar(dispatcher, static_cast<int32>(var.value.as_id)));
485  }
486
487  // Have this host var, look up the object.
488  VarMap::iterator ret = live_vars_.find(found->second);
489
490  // We CHECK here because we currently don't fall back sanely.
491  // This may be involved in a NULL dereference. http://crbug.com/276347
492  CHECK(ret != live_vars_.end());
493
494  // All objects should be proxy objects.
495  DCHECK(ret->second.var->AsProxyObjectVar());
496  return scoped_refptr<ProxyObjectVar>(ret->second.var->AsProxyObjectVar());
497}
498
499int PluginVarTracker::TrackSharedMemoryHandle(PP_Instance instance,
500                                              base::SharedMemoryHandle handle,
501                                              uint32 size_in_bytes) {
502  NOTREACHED();
503  return -1;
504}
505
506bool PluginVarTracker::StopTrackingSharedMemoryHandle(
507    int id,
508    PP_Instance instance,
509    base::SharedMemoryHandle* handle,
510    uint32* size_in_bytes) {
511  NOTREACHED();
512  return false;
513}
514
515}  // namesace proxy
516}  // namespace ppapi
517