// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "ppapi/proxy/plugin_var_tracker.h" #include #include "base/memory/ref_counted.h" #include "base/memory/singleton.h" #include "ipc/ipc_message.h" #include "ppapi/c/dev/ppp_class_deprecated.h" #include "ppapi/c/ppb_var.h" #include "ppapi/proxy/file_system_resource.h" #include "ppapi/proxy/media_stream_audio_track_resource.h" #include "ppapi/proxy/media_stream_video_track_resource.h" #include "ppapi/proxy/plugin_array_buffer_var.h" #include "ppapi/proxy/plugin_dispatcher.h" #include "ppapi/proxy/plugin_globals.h" #include "ppapi/proxy/plugin_resource_var.h" #include "ppapi/proxy/ppapi_messages.h" #include "ppapi/proxy/proxy_object_var.h" #include "ppapi/shared_impl/api_id.h" #include "ppapi/shared_impl/ppapi_globals.h" #include "ppapi/shared_impl/proxy_lock.h" #include "ppapi/shared_impl/resource_tracker.h" #include "ppapi/shared_impl/var.h" namespace ppapi { namespace proxy { namespace { Connection GetConnectionForInstance(PP_Instance instance) { PluginDispatcher* dispatcher = PluginDispatcher::GetForInstance(instance); DCHECK(dispatcher); return Connection(PluginGlobals::Get()->GetBrowserSender(), dispatcher); } } // namespace PluginVarTracker::HostVar::HostVar(PluginDispatcher* d, int32_t i) : dispatcher(d), host_object_id(i) {} bool PluginVarTracker::HostVar::operator<(const HostVar& other) const { if (dispatcher < other.dispatcher) return true; if (other.dispatcher < dispatcher) return false; return host_object_id < other.host_object_id; } PluginVarTracker::PluginVarTracker() : VarTracker(THREAD_SAFE) { } PluginVarTracker::~PluginVarTracker() { } PP_Var PluginVarTracker::ReceiveObjectPassRef(const PP_Var& host_var, PluginDispatcher* dispatcher) { CheckThreadingPreconditions(); DCHECK(host_var.type == PP_VARTYPE_OBJECT); // Get the object. scoped_refptr object( FindOrMakePluginVarFromHostVar(host_var, dispatcher)); // Actually create the PP_Var, this will add all the tracking info but not // adjust any refcounts. PP_Var ret = GetOrCreateObjectVarID(object.get()); VarInfo& info = GetLiveVar(ret)->second; if (info.ref_count > 0) { // We already had a reference to it before. That means the renderer now has // two references on our behalf. We want to transfer that extra reference // to our list. This means we addref in the plugin, and release the extra // one in the renderer. SendReleaseObjectMsg(*object.get()); } info.ref_count++; return ret; } PP_Var PluginVarTracker::TrackObjectWithNoReference( const PP_Var& host_var, PluginDispatcher* dispatcher) { CheckThreadingPreconditions(); DCHECK(host_var.type == PP_VARTYPE_OBJECT); // Get the object. scoped_refptr object( FindOrMakePluginVarFromHostVar(host_var, dispatcher)); // Actually create the PP_Var, this will add all the tracking info but not // adjust any refcounts. PP_Var ret = GetOrCreateObjectVarID(object.get()); VarInfo& info = GetLiveVar(ret)->second; info.track_with_no_reference_count++; return ret; } void PluginVarTracker::StopTrackingObjectWithNoReference( const PP_Var& plugin_var) { CheckThreadingPreconditions(); DCHECK(plugin_var.type == PP_VARTYPE_OBJECT); VarMap::iterator found = GetLiveVar(plugin_var); if (found == live_vars_.end()) { NOTREACHED(); return; } DCHECK(found->second.track_with_no_reference_count > 0); found->second.track_with_no_reference_count--; DeleteObjectInfoIfNecessary(found); } PP_Var PluginVarTracker::GetHostObject(const PP_Var& plugin_object) const { CheckThreadingPreconditions(); if (plugin_object.type != PP_VARTYPE_OBJECT) { NOTREACHED(); return PP_MakeUndefined(); } Var* var = GetVar(plugin_object); ProxyObjectVar* object = var->AsProxyObjectVar(); if (!object) { NOTREACHED(); return PP_MakeUndefined(); } // Make a var with the host ID. PP_Var ret = { PP_VARTYPE_OBJECT }; ret.value.as_id = object->host_var_id(); return ret; } PluginDispatcher* PluginVarTracker::DispatcherForPluginObject( const PP_Var& plugin_object) const { CheckThreadingPreconditions(); if (plugin_object.type != PP_VARTYPE_OBJECT) return NULL; VarMap::const_iterator found = GetLiveVar(plugin_object); if (found == live_vars_.end()) return NULL; ProxyObjectVar* object = found->second.var->AsProxyObjectVar(); if (!object) return NULL; return object->dispatcher(); } void PluginVarTracker::ReleaseHostObject(PluginDispatcher* dispatcher, const PP_Var& host_object) { CheckThreadingPreconditions(); DCHECK(host_object.type == PP_VARTYPE_OBJECT); // Convert the host object to a normal var valid in the plugin. HostVarToPluginVarMap::iterator found = host_var_to_plugin_var_.find( HostVar(dispatcher, static_cast(host_object.value.as_id))); if (found == host_var_to_plugin_var_.end()) { NOTREACHED(); return; } // Now just release the object given the plugin var ID. ReleaseVar(found->second); } PP_Var PluginVarTracker::MakeResourcePPVarFromMessage( PP_Instance instance, const IPC::Message& creation_message, int pending_renderer_id, int pending_browser_id) { switch (creation_message.type()) { case PpapiPluginMsg_FileSystem_CreateFromPendingHost::ID: { DCHECK(pending_renderer_id); DCHECK(pending_browser_id); PP_FileSystemType file_system_type; if (!UnpackMessage( creation_message, &file_system_type)) { NOTREACHED() << "Invalid message of type " "PpapiPluginMsg_FileSystem_CreateFromPendingHost"; return PP_MakeNull(); } // Create a plugin-side resource and attach it to the host resource. // Note: This only makes sense when the plugin is out of process (which // should always be true when passing resource vars). PP_Resource pp_resource = (new FileSystemResource(GetConnectionForInstance(instance), instance, pending_renderer_id, pending_browser_id, file_system_type))->GetReference(); return MakeResourcePPVar(pp_resource); } case PpapiPluginMsg_MediaStreamAudioTrack_CreateFromPendingHost::ID: { DCHECK(pending_renderer_id); std::string track_id; if (!UnpackMessage< PpapiPluginMsg_MediaStreamAudioTrack_CreateFromPendingHost>( creation_message, &track_id)) { NOTREACHED() << "Invalid message of type " "PpapiPluginMsg_MediaStreamAudioTrack_CreateFromPendingHost"; return PP_MakeNull(); } PP_Resource pp_resource = (new MediaStreamAudioTrackResource(GetConnectionForInstance(instance), instance, pending_renderer_id, track_id))->GetReference(); return MakeResourcePPVar(pp_resource); } case PpapiPluginMsg_MediaStreamVideoTrack_CreateFromPendingHost::ID: { DCHECK(pending_renderer_id); std::string track_id; if (!UnpackMessage< PpapiPluginMsg_MediaStreamVideoTrack_CreateFromPendingHost>( creation_message, &track_id)) { NOTREACHED() << "Invalid message of type " "PpapiPluginMsg_MediaStreamVideoTrack_CreateFromPendingHost"; return PP_MakeNull(); } PP_Resource pp_resource = (new MediaStreamVideoTrackResource(GetConnectionForInstance(instance), instance, pending_renderer_id, track_id))->GetReference(); return MakeResourcePPVar(pp_resource); } default: { NOTREACHED() << "Creation message has unexpected type " << creation_message.type(); return PP_MakeNull(); } } } ResourceVar* PluginVarTracker::MakeResourceVar(PP_Resource pp_resource) { // The resource 0 returns a null resource var. if (!pp_resource) return new PluginResourceVar(); ResourceTracker* resource_tracker = PpapiGlobals::Get()->GetResourceTracker(); ppapi::Resource* resource = resource_tracker->GetResource(pp_resource); // A non-existant resource other than 0 returns NULL. if (!resource) return NULL; return new PluginResourceVar(resource); } void PluginVarTracker::DidDeleteInstance(PP_Instance instance) { // Calling the destructors on plugin objects may in turn release other // objects which will mutate the map out from under us. So do a two-step // process of identifying the ones to delete, and then delete them. // // See the comment above user_data_to_plugin_ in the header file. We assume // there aren't that many objects so a brute-force search is reasonable. std::vector user_data_to_delete; for (UserDataToPluginImplementedVarMap::const_iterator i = user_data_to_plugin_.begin(); i != user_data_to_plugin_.end(); ++i) { if (i->second.instance == instance) user_data_to_delete.push_back(i->first); } for (size_t i = 0; i < user_data_to_delete.size(); i++) { UserDataToPluginImplementedVarMap::iterator found = user_data_to_plugin_.find(user_data_to_delete[i]); if (found == user_data_to_plugin_.end()) continue; // Object removed from list while we were iterating. if (!found->second.plugin_object_id) { // This object is for the freed instance and the plugin is not holding // any references to it. Deallocate immediately. CallWhileUnlocked(found->second.ppp_class->Deallocate, found->first); user_data_to_plugin_.erase(found); } else { // The plugin is holding refs to this object. We don't want to call // Deallocate since the plugin may be depending on those refs to keep // its data alive. To avoid crashes in this case, just clear out the // instance to mark it and continue. When the plugin refs go to 0, // we'll notice there is no instance and call Deallocate. found->second.instance = 0; } } } void PluginVarTracker::DidDeleteDispatcher(PluginDispatcher* dispatcher) { for (VarMap::iterator it = live_vars_.begin(); it != live_vars_.end(); ++it) { if (it->second.var.get() == NULL) continue; ProxyObjectVar* object = it->second.var->AsProxyObjectVar(); if (object && object->dispatcher() == dispatcher) object->clear_dispatcher(); } } ArrayBufferVar* PluginVarTracker::CreateArrayBuffer(uint32_t size_in_bytes) { return new PluginArrayBufferVar(size_in_bytes); } ArrayBufferVar* PluginVarTracker::CreateShmArrayBuffer( uint32_t size_in_bytes, base::SharedMemoryHandle handle) { return new PluginArrayBufferVar(size_in_bytes, handle); } void PluginVarTracker::PluginImplementedObjectCreated( PP_Instance instance, const PP_Var& created_var, const PPP_Class_Deprecated* ppp_class, void* ppp_class_data) { PluginImplementedVar p; p.ppp_class = ppp_class; p.instance = instance; p.plugin_object_id = created_var.value.as_id; user_data_to_plugin_[ppp_class_data] = p; // Link the user data to the object. ProxyObjectVar* object = GetVar(created_var)->AsProxyObjectVar(); object->set_user_data(ppp_class_data); } void PluginVarTracker::PluginImplementedObjectDestroyed(void* user_data) { UserDataToPluginImplementedVarMap::iterator found = user_data_to_plugin_.find(user_data); if (found == user_data_to_plugin_.end()) { NOTREACHED(); return; } user_data_to_plugin_.erase(found); } bool PluginVarTracker::IsPluginImplementedObjectAlive(void* user_data) { return user_data_to_plugin_.find(user_data) != user_data_to_plugin_.end(); } bool PluginVarTracker::ValidatePluginObjectCall( const PPP_Class_Deprecated* ppp_class, void* user_data) { UserDataToPluginImplementedVarMap::iterator found = user_data_to_plugin_.find(user_data); if (found == user_data_to_plugin_.end()) return false; return found->second.ppp_class == ppp_class; } int32_t PluginVarTracker::AddVarInternal(Var* var, AddVarRefMode mode) { // Normal adding. int32_t new_id = VarTracker::AddVarInternal(var, mode); // Need to add proxy objects to the host var map. ProxyObjectVar* proxy_object = var->AsProxyObjectVar(); if (proxy_object) { HostVar host_var(proxy_object->dispatcher(), proxy_object->host_var_id()); // TODO(teravest): Change to DCHECK when http://crbug.com/276347 is // resolved. CHECK(host_var_to_plugin_var_.find(host_var) == host_var_to_plugin_var_.end()); // Adding an object twice, use // FindOrMakePluginVarFromHostVar. host_var_to_plugin_var_[host_var] = new_id; } return new_id; } void PluginVarTracker::TrackedObjectGettingOneRef(VarMap::const_iterator iter) { ProxyObjectVar* object = iter->second.var->AsProxyObjectVar(); if (!object) { NOTREACHED(); return; } DCHECK(iter->second.ref_count == 0); // Got an AddRef for an object we have no existing reference for. // We need to tell the browser we've taken a ref. This comes up when the // browser passes an object as an input param and holds a ref for us. // This must be a sync message since otherwise the "addref" will actually // occur after the return to the browser of the sync function that // presumably sent the object. SendAddRefObjectMsg(*object); } void PluginVarTracker::ObjectGettingZeroRef(VarMap::iterator iter) { ProxyObjectVar* object = iter->second.var->AsProxyObjectVar(); if (!object) { NOTREACHED(); return; } // Notify the host we're no longer holding our ref. DCHECK(iter->second.ref_count == 0); SendReleaseObjectMsg(*object); UserDataToPluginImplementedVarMap::iterator found = user_data_to_plugin_.find(object->user_data()); if (found != user_data_to_plugin_.end()) { // This object is implemented in the plugin. if (found->second.instance == 0) { // Instance is destroyed. This means that we'll never get a Deallocate // call from the renderer and we should do so now. CallWhileUnlocked(found->second.ppp_class->Deallocate, found->first); user_data_to_plugin_.erase(found); } else { // The plugin is releasing its last reference to an object it implements. // Clear the tracking data that links our "plugin implemented object" to // the var. If the instance is destroyed and there is no ID, we know that // we should just call Deallocate on the object data. // // See the plugin_object_id declaration for more info. found->second.plugin_object_id = 0; } } // This will optionally delete the info from live_vars_. VarTracker::ObjectGettingZeroRef(iter); } bool PluginVarTracker::DeleteObjectInfoIfNecessary(VarMap::iterator iter) { // Get the info before calling the base class's version of this function, // which may delete the object. ProxyObjectVar* object = iter->second.var->AsProxyObjectVar(); HostVar host_var(object->dispatcher(), object->host_var_id()); if (!VarTracker::DeleteObjectInfoIfNecessary(iter)) return false; // Clean up the host var mapping. DCHECK(host_var_to_plugin_var_.find(host_var) != host_var_to_plugin_var_.end()); host_var_to_plugin_var_.erase(host_var); return true; } PP_Var PluginVarTracker::GetOrCreateObjectVarID(ProxyObjectVar* object) { // We can't use object->GetPPVar() because we don't want to affect the // refcount, so we have to add everything manually here. int32_t var_id = object->GetExistingVarID(); if (!var_id) { var_id = AddVarInternal(object, ADD_VAR_CREATE_WITH_NO_REFERENCE); object->AssignVarID(var_id); } PP_Var ret = { PP_VARTYPE_OBJECT }; ret.value.as_id = var_id; return ret; } void PluginVarTracker::SendAddRefObjectMsg( const ProxyObjectVar& proxy_object) { if (proxy_object.dispatcher()) { proxy_object.dispatcher()->Send(new PpapiHostMsg_PPBVar_AddRefObject( API_ID_PPB_VAR_DEPRECATED, proxy_object.host_var_id())); } } void PluginVarTracker::SendReleaseObjectMsg( const ProxyObjectVar& proxy_object) { if (proxy_object.dispatcher()) { proxy_object.dispatcher()->Send(new PpapiHostMsg_PPBVar_ReleaseObject( API_ID_PPB_VAR_DEPRECATED, proxy_object.host_var_id())); } } scoped_refptr PluginVarTracker::FindOrMakePluginVarFromHostVar( const PP_Var& var, PluginDispatcher* dispatcher) { DCHECK(var.type == PP_VARTYPE_OBJECT); HostVar host_var(dispatcher, var.value.as_id); HostVarToPluginVarMap::iterator found = host_var_to_plugin_var_.find(host_var); if (found == host_var_to_plugin_var_.end()) { // Create a new object. return scoped_refptr( new ProxyObjectVar(dispatcher, static_cast(var.value.as_id))); } // Have this host var, look up the object. VarMap::iterator ret = live_vars_.find(found->second); // We CHECK here because we currently don't fall back sanely. // This may be involved in a NULL dereference. http://crbug.com/276347 CHECK(ret != live_vars_.end()); // All objects should be proxy objects. DCHECK(ret->second.var->AsProxyObjectVar()); return scoped_refptr(ret->second.var->AsProxyObjectVar()); } int PluginVarTracker::TrackSharedMemoryHandle(PP_Instance instance, base::SharedMemoryHandle handle, uint32_t size_in_bytes) { NOTREACHED(); return -1; } bool PluginVarTracker::StopTrackingSharedMemoryHandle( int id, PP_Instance instance, base::SharedMemoryHandle* handle, uint32_t* size_in_bytes) { NOTREACHED(); return false; } } // namesace proxy } // namespace ppapi