| // 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 "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/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 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<ProxyObjectVar> 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<ProxyObjectVar> 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<int32>(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) { |
| DCHECK(pending_renderer_id); |
| DCHECK(pending_browser_id); |
| switch (creation_message.type()) { |
| case PpapiPluginMsg_FileSystem_CreateFromPendingHost::ID: { |
| PP_FileSystemType file_system_type; |
| if (!UnpackMessage<PpapiPluginMsg_FileSystem_CreateFromPendingHost>( |
| 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); |
| } |
| 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<void*> 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 size_in_bytes) { |
| return new PluginArrayBufferVar(size_in_bytes); |
| } |
| |
| ArrayBufferVar* PluginVarTracker::CreateShmArrayBuffer( |
| uint32 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 PluginVarTracker::AddVarInternal(Var* var, AddVarRefMode mode) { |
| // Normal adding. |
| int32 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. |
| 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 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) { |
| int unused; |
| if (proxy_object.dispatcher()) { |
| proxy_object.dispatcher()->Send(new PpapiHostMsg_PPBVar_AddRefObject( |
| API_ID_PPB_VAR_DEPRECATED, proxy_object.host_var_id(), &unused)); |
| } |
| } |
| |
| 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<ProxyObjectVar> 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<ProxyObjectVar>( |
| new ProxyObjectVar(dispatcher, static_cast<int32>(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<ProxyObjectVar>(ret->second.var->AsProxyObjectVar()); |
| } |
| |
| int PluginVarTracker::TrackSharedMemoryHandle(PP_Instance instance, |
| base::SharedMemoryHandle handle, |
| uint32 size_in_bytes) { |
| NOTREACHED(); |
| return -1; |
| } |
| |
| bool PluginVarTracker::StopTrackingSharedMemoryHandle( |
| int id, |
| PP_Instance instance, |
| base::SharedMemoryHandle* handle, |
| uint32* size_in_bytes) { |
| NOTREACHED(); |
| return false; |
| } |
| |
| } // namesace proxy |
| } // namespace ppapi |