// 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 #include #include "ipc/ipc_test_sink.h" #include "ppapi/c/dev/ppp_class_deprecated.h" #include "ppapi/proxy/plugin_var_tracker.h" #include "ppapi/proxy/ppapi_messages.h" #include "ppapi/proxy/ppapi_proxy_test.h" #include "ppapi/proxy/proxy_object_var.h" #include "ppapi/shared_impl/proxy_lock.h" namespace ppapi { namespace proxy { namespace { PP_Var MakeObject(int32_t object_id) { PP_Var ret; ret.type = PP_VARTYPE_OBJECT; ret.value.as_id = object_id; return ret; } // A Deallocate() function for PPP_Class that just increments the integer // referenced by the pointer so we know how often Deallocate was called. void MarkOnDeallocate(void* object) { (*static_cast(object))++; } // A class that just implements MarkOnDeallocate on destruction. PPP_Class_Deprecated mark_on_deallocate_class = { NULL, // HasProperty, NULL, // HasMethod, NULL, // GetProperty, NULL, // GetAllPropertyNames, NULL, // SetProperty, NULL, // RemoveProperty, NULL, // Call, NULL, // Construct, &MarkOnDeallocate }; } // namespace class PluginVarTrackerTest : public PluginProxyTest { public: PluginVarTrackerTest() {} protected: // Asserts that there is a unique "release object" IPC message in the test // sink. This will return the var ID from the message or -1 if none found. int32_t GetObjectIDForUniqueReleaseObject() { const IPC::Message* release_msg = sink().GetUniqueMessageMatching( PpapiHostMsg_PPBVar_ReleaseObject::ID); if (!release_msg) return -1; std::tuple id; PpapiHostMsg_PPBVar_ReleaseObject::Read(release_msg, &id); return std::get<0>(id); } }; TEST_F(PluginVarTrackerTest, GetHostObject) { ProxyAutoLock lock; PP_Var host_object = MakeObject(12345); // Round-trip through the tracker to make sure the host object comes out the // other end. PP_Var plugin_object = var_tracker().ReceiveObjectPassRef( host_object, plugin_dispatcher()); PP_Var host_object2 = var_tracker().GetHostObject(plugin_object); EXPECT_EQ(PP_VARTYPE_OBJECT, host_object2.type); EXPECT_EQ(host_object.value.as_id, host_object2.value.as_id); var_tracker().ReleaseVar(plugin_object); } TEST_F(PluginVarTrackerTest, ReceiveObjectPassRef) { ProxyAutoLock lock; PP_Var host_object = MakeObject(12345); // Receive the object, we should have one ref and no messages. PP_Var plugin_object = var_tracker().ReceiveObjectPassRef( host_object, plugin_dispatcher()); EXPECT_EQ(0u, sink().message_count()); EXPECT_EQ(1, var_tracker().GetRefCountForObject(plugin_object)); EXPECT_EQ(0, var_tracker().GetTrackedWithNoReferenceCountForObject(plugin_object)); // Receive the same object again, we should get the same plugin ID out. PP_Var plugin_object2 = var_tracker().ReceiveObjectPassRef( host_object, plugin_dispatcher()); EXPECT_EQ(plugin_object.value.as_id, plugin_object2.value.as_id); EXPECT_EQ(2, var_tracker().GetRefCountForObject(plugin_object)); EXPECT_EQ(0, var_tracker().GetTrackedWithNoReferenceCountForObject(plugin_object)); // It should have sent one message to decerment the refcount in the host. // This is because it only maintains one host refcount for all references // in the plugin, but the host just sent the second one. EXPECT_EQ(host_object.value.as_id, GetObjectIDForUniqueReleaseObject()); sink().ClearMessages(); // Release the object, one ref at a time. The second release should free // the tracking data and send a release message to the browser. var_tracker().ReleaseVar(plugin_object); EXPECT_EQ(1, var_tracker().GetRefCountForObject(plugin_object)); var_tracker().ReleaseVar(plugin_object); EXPECT_EQ(-1, var_tracker().GetRefCountForObject(plugin_object)); EXPECT_EQ(host_object.value.as_id, GetObjectIDForUniqueReleaseObject()); } // Tests freeing objects that have both refcounts and "tracked with no ref". TEST_F(PluginVarTrackerTest, FreeTrackedAndReferencedObject) { ProxyAutoLock lock; PP_Var host_object = MakeObject(12345); // Phase one: First receive via a "pass ref", then a tracked with no ref. PP_Var plugin_var = var_tracker().ReceiveObjectPassRef( host_object, plugin_dispatcher()); PP_Var plugin_var2 = var_tracker().TrackObjectWithNoReference( host_object, plugin_dispatcher()); EXPECT_EQ(plugin_var.value.as_id, plugin_var2.value.as_id); EXPECT_EQ(1, var_tracker().GetRefCountForObject(plugin_var)); EXPECT_EQ(1, var_tracker().GetTrackedWithNoReferenceCountForObject(plugin_var)); // Free via the refcount, this should release the object to the browser but // maintain the tracked object. var_tracker().ReleaseVar(plugin_var); EXPECT_EQ(0, var_tracker().GetRefCountForObject(plugin_var)); EXPECT_EQ(1u, sink().message_count()); EXPECT_EQ(host_object.value.as_id, GetObjectIDForUniqueReleaseObject()); // Now free via the tracked object, this should free it. var_tracker().StopTrackingObjectWithNoReference(plugin_var); EXPECT_EQ(-1, var_tracker().GetRefCountForObject(plugin_var)); // Phase two: Receive via a tracked, then get an addref. sink().ClearMessages(); plugin_var = var_tracker().TrackObjectWithNoReference( host_object, plugin_dispatcher()); plugin_var2 = var_tracker().ReceiveObjectPassRef( host_object, plugin_dispatcher()); EXPECT_EQ(plugin_var.value.as_id, plugin_var2.value.as_id); EXPECT_EQ(1, var_tracker().GetRefCountForObject(plugin_var)); EXPECT_EQ(1, var_tracker().GetTrackedWithNoReferenceCountForObject(plugin_var)); // Free via the tracked object, this should have no effect. var_tracker().StopTrackingObjectWithNoReference(plugin_var); EXPECT_EQ(0, var_tracker().GetTrackedWithNoReferenceCountForObject(plugin_var)); EXPECT_EQ(0u, sink().message_count()); // Now free via the refcount, this should delete it. var_tracker().ReleaseVar(plugin_var); EXPECT_EQ(-1, var_tracker().GetRefCountForObject(plugin_var)); EXPECT_EQ(host_object.value.as_id, GetObjectIDForUniqueReleaseObject()); } TEST_F(PluginVarTrackerTest, RecursiveTrackWithNoRef) { ProxyAutoLock lock; PP_Var host_object = MakeObject(12345); // Receive a tracked object twice. PP_Var plugin_var = var_tracker().TrackObjectWithNoReference( host_object, plugin_dispatcher()); EXPECT_EQ(1, var_tracker().GetTrackedWithNoReferenceCountForObject(plugin_var)); PP_Var plugin_var2 = var_tracker().TrackObjectWithNoReference( host_object, plugin_dispatcher()); EXPECT_EQ(plugin_var.value.as_id, plugin_var2.value.as_id); EXPECT_EQ(0, var_tracker().GetRefCountForObject(plugin_var)); EXPECT_EQ(2, var_tracker().GetTrackedWithNoReferenceCountForObject(plugin_var)); // Now release those tracked items, the reference should be freed. var_tracker().StopTrackingObjectWithNoReference(plugin_var); EXPECT_EQ(1, var_tracker().GetTrackedWithNoReferenceCountForObject(plugin_var)); var_tracker().StopTrackingObjectWithNoReference(plugin_var); EXPECT_EQ(-1, var_tracker().GetTrackedWithNoReferenceCountForObject(plugin_var)); } // Tests that objects implemented by the plugin that have no references by // the plugin get their Deallocate function called on destruction. TEST_F(PluginVarTrackerTest, PluginObjectInstanceDeleted) { ProxyAutoLock lock; PP_Var host_object = MakeObject(12345); PP_Instance pp_instance = 0x12345; int deallocate_called = 0; void* user_data = &deallocate_called; // Make a var with one reference. scoped_refptr object( new ProxyObjectVar(plugin_dispatcher(), host_object.value.as_id)); PP_Var plugin_var = MakeObject(var_tracker().AddVar(object.get())); var_tracker().PluginImplementedObjectCreated( pp_instance, plugin_var, &mark_on_deallocate_class, user_data); // Release the plugin ref to the var. WebKit hasn't called destroy so // we won't get a destroy call. object = NULL; var_tracker().ReleaseVar(plugin_var); EXPECT_EQ(0, deallocate_called); // Synthesize an instance destuction, this should call Deallocate. var_tracker().DidDeleteInstance(pp_instance); EXPECT_EQ(1, deallocate_called); } // Tests what happens when a plugin keeps a ref to a plugin-implemented // object var longer than the instance. We should not call the destructor until // the plugin releases its last ref. TEST_F(PluginVarTrackerTest, PluginObjectLeaked) { ProxyAutoLock lock; PP_Var host_object = MakeObject(12345); PP_Instance pp_instance = 0x12345; int deallocate_called = 0; void* user_data = &deallocate_called; // Make a var with one reference. scoped_refptr object( new ProxyObjectVar(plugin_dispatcher(), host_object.value.as_id)); PP_Var plugin_var = MakeObject(var_tracker().AddVar(object.get())); var_tracker().PluginImplementedObjectCreated( pp_instance, plugin_var, &mark_on_deallocate_class, user_data); // Destroy the instance. This should not call deallocate since the plugin // still has a ref. var_tracker().DidDeleteInstance(pp_instance); EXPECT_EQ(0, deallocate_called); // Release the plugin ref to the var. Since the instance is gone this should // call deallocate. object = NULL; var_tracker().ReleaseVar(plugin_var); EXPECT_EQ(1, deallocate_called); } } // namespace proxy } // namespace ppapi