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 "ipc/ipc_test_sink.h" 6 #include "ppapi/c/dev/ppp_class_deprecated.h" 7 #include "ppapi/proxy/plugin_var_tracker.h" 8 #include "ppapi/proxy/ppapi_messages.h" 9 #include "ppapi/proxy/ppapi_proxy_test.h" 10 #include "ppapi/proxy/proxy_object_var.h" 11 #include "ppapi/shared_impl/proxy_lock.h" 12 13 namespace ppapi { 14 namespace proxy { 15 16 namespace { 17 18 PP_Var MakeObject(int32 object_id) { 19 PP_Var ret; 20 ret.type = PP_VARTYPE_OBJECT; 21 ret.value.as_id = object_id; 22 return ret; 23 } 24 25 // A Deallocate() function for PPP_Class that just increments the integer 26 // referenced by the pointer so we know how often Deallocate was called. 27 void MarkOnDeallocate(void* object) { 28 (*static_cast<int*>(object))++; 29 } 30 31 // A class that just implements MarkOnDeallocate on destruction. 32 PPP_Class_Deprecated mark_on_deallocate_class = { 33 NULL, // HasProperty, 34 NULL, // HasMethod, 35 NULL, // GetProperty, 36 NULL, // GetAllPropertyNames, 37 NULL, // SetProperty, 38 NULL, // RemoveProperty, 39 NULL, // Call, 40 NULL, // Construct, 41 &MarkOnDeallocate 42 }; 43 44 } // namespace 45 46 class PluginVarTrackerTest : public PluginProxyTest { 47 public: 48 PluginVarTrackerTest() {} 49 50 protected: 51 // Asserts that there is a unique "release object" IPC message in the test 52 // sink. This will return the var ID from the message or -1 if none found. 53 int32 GetObjectIDForUniqueReleaseObject() { 54 const IPC::Message* release_msg = sink().GetUniqueMessageMatching( 55 PpapiHostMsg_PPBVar_ReleaseObject::ID); 56 if (!release_msg) 57 return -1; 58 59 Tuple1<int64> id; 60 PpapiHostMsg_PPBVar_ReleaseObject::Read(release_msg, &id); 61 return id.a; 62 } 63 }; 64 65 TEST_F(PluginVarTrackerTest, GetHostObject) { 66 ProxyAutoLock lock; 67 PP_Var host_object = MakeObject(12345); 68 69 // Round-trip through the tracker to make sure the host object comes out the 70 // other end. 71 PP_Var plugin_object = var_tracker().ReceiveObjectPassRef( 72 host_object, plugin_dispatcher()); 73 PP_Var host_object2 = var_tracker().GetHostObject(plugin_object); 74 EXPECT_EQ(PP_VARTYPE_OBJECT, host_object2.type); 75 EXPECT_EQ(host_object.value.as_id, host_object2.value.as_id); 76 77 var_tracker().ReleaseVar(plugin_object); 78 } 79 80 TEST_F(PluginVarTrackerTest, ReceiveObjectPassRef) { 81 ProxyAutoLock lock; 82 PP_Var host_object = MakeObject(12345); 83 84 // Receive the object, we should have one ref and no messages. 85 PP_Var plugin_object = var_tracker().ReceiveObjectPassRef( 86 host_object, plugin_dispatcher()); 87 EXPECT_EQ(0u, sink().message_count()); 88 EXPECT_EQ(1, var_tracker().GetRefCountForObject(plugin_object)); 89 EXPECT_EQ(0, 90 var_tracker().GetTrackedWithNoReferenceCountForObject(plugin_object)); 91 92 // Receive the same object again, we should get the same plugin ID out. 93 PP_Var plugin_object2 = var_tracker().ReceiveObjectPassRef( 94 host_object, plugin_dispatcher()); 95 EXPECT_EQ(plugin_object.value.as_id, plugin_object2.value.as_id); 96 EXPECT_EQ(2, var_tracker().GetRefCountForObject(plugin_object)); 97 EXPECT_EQ(0, 98 var_tracker().GetTrackedWithNoReferenceCountForObject(plugin_object)); 99 100 // It should have sent one message to decerment the refcount in the host. 101 // This is because it only maintains one host refcount for all references 102 // in the plugin, but the host just sent the second one. 103 EXPECT_EQ(host_object.value.as_id, GetObjectIDForUniqueReleaseObject()); 104 sink().ClearMessages(); 105 106 // Release the object, one ref at a time. The second release should free 107 // the tracking data and send a release message to the browser. 108 var_tracker().ReleaseVar(plugin_object); 109 EXPECT_EQ(1, var_tracker().GetRefCountForObject(plugin_object)); 110 var_tracker().ReleaseVar(plugin_object); 111 EXPECT_EQ(-1, var_tracker().GetRefCountForObject(plugin_object)); 112 EXPECT_EQ(host_object.value.as_id, GetObjectIDForUniqueReleaseObject()); 113 } 114 115 // Tests freeing objects that have both refcounts and "tracked with no ref". 116 TEST_F(PluginVarTrackerTest, FreeTrackedAndReferencedObject) { 117 ProxyAutoLock lock; 118 PP_Var host_object = MakeObject(12345); 119 120 // Phase one: First receive via a "pass ref", then a tracked with no ref. 121 PP_Var plugin_var = var_tracker().ReceiveObjectPassRef( 122 host_object, plugin_dispatcher()); 123 PP_Var plugin_var2 = var_tracker().TrackObjectWithNoReference( 124 host_object, plugin_dispatcher()); 125 EXPECT_EQ(plugin_var.value.as_id, plugin_var2.value.as_id); 126 EXPECT_EQ(1, var_tracker().GetRefCountForObject(plugin_var)); 127 EXPECT_EQ(1, 128 var_tracker().GetTrackedWithNoReferenceCountForObject(plugin_var)); 129 130 // Free via the refcount, this should release the object to the browser but 131 // maintain the tracked object. 132 var_tracker().ReleaseVar(plugin_var); 133 EXPECT_EQ(0, var_tracker().GetRefCountForObject(plugin_var)); 134 EXPECT_EQ(1u, sink().message_count()); 135 EXPECT_EQ(host_object.value.as_id, GetObjectIDForUniqueReleaseObject()); 136 137 // Now free via the tracked object, this should free it. 138 var_tracker().StopTrackingObjectWithNoReference(plugin_var); 139 EXPECT_EQ(-1, var_tracker().GetRefCountForObject(plugin_var)); 140 141 // Phase two: Receive via a tracked, then get an addref. 142 sink().ClearMessages(); 143 plugin_var = var_tracker().TrackObjectWithNoReference( 144 host_object, plugin_dispatcher()); 145 plugin_var2 = var_tracker().ReceiveObjectPassRef( 146 host_object, plugin_dispatcher()); 147 EXPECT_EQ(plugin_var.value.as_id, plugin_var2.value.as_id); 148 EXPECT_EQ(1, var_tracker().GetRefCountForObject(plugin_var)); 149 EXPECT_EQ(1, 150 var_tracker().GetTrackedWithNoReferenceCountForObject(plugin_var)); 151 152 // Free via the tracked object, this should have no effect. 153 var_tracker().StopTrackingObjectWithNoReference(plugin_var); 154 EXPECT_EQ(0, 155 var_tracker().GetTrackedWithNoReferenceCountForObject(plugin_var)); 156 EXPECT_EQ(0u, sink().message_count()); 157 158 // Now free via the refcount, this should delete it. 159 var_tracker().ReleaseVar(plugin_var); 160 EXPECT_EQ(-1, var_tracker().GetRefCountForObject(plugin_var)); 161 EXPECT_EQ(host_object.value.as_id, GetObjectIDForUniqueReleaseObject()); 162 } 163 164 TEST_F(PluginVarTrackerTest, RecursiveTrackWithNoRef) { 165 ProxyAutoLock lock; 166 PP_Var host_object = MakeObject(12345); 167 168 // Receive a tracked object twice. 169 PP_Var plugin_var = var_tracker().TrackObjectWithNoReference( 170 host_object, plugin_dispatcher()); 171 EXPECT_EQ(1, 172 var_tracker().GetTrackedWithNoReferenceCountForObject(plugin_var)); 173 PP_Var plugin_var2 = var_tracker().TrackObjectWithNoReference( 174 host_object, plugin_dispatcher()); 175 EXPECT_EQ(plugin_var.value.as_id, plugin_var2.value.as_id); 176 EXPECT_EQ(0, var_tracker().GetRefCountForObject(plugin_var)); 177 EXPECT_EQ(2, 178 var_tracker().GetTrackedWithNoReferenceCountForObject(plugin_var)); 179 180 // Now release those tracked items, the reference should be freed. 181 var_tracker().StopTrackingObjectWithNoReference(plugin_var); 182 EXPECT_EQ(1, 183 var_tracker().GetTrackedWithNoReferenceCountForObject(plugin_var)); 184 var_tracker().StopTrackingObjectWithNoReference(plugin_var); 185 EXPECT_EQ(-1, 186 var_tracker().GetTrackedWithNoReferenceCountForObject(plugin_var)); 187 } 188 189 // Tests that objects implemented by the plugin that have no references by 190 // the plugin get their Deallocate function called on destruction. 191 TEST_F(PluginVarTrackerTest, PluginObjectInstanceDeleted) { 192 ProxyAutoLock lock; 193 PP_Var host_object = MakeObject(12345); 194 PP_Instance pp_instance = 0x12345; 195 196 int deallocate_called = 0; 197 void* user_data = &deallocate_called; 198 199 // Make a var with one reference. 200 scoped_refptr<ProxyObjectVar> object( 201 new ProxyObjectVar(plugin_dispatcher(), host_object.value.as_id)); 202 PP_Var plugin_var = MakeObject(var_tracker().AddVar(object.get())); 203 var_tracker().PluginImplementedObjectCreated( 204 pp_instance, plugin_var, &mark_on_deallocate_class, user_data); 205 206 // Release the plugin ref to the var. WebKit hasn't called destroy so 207 // we won't get a destroy call. 208 object = NULL; 209 var_tracker().ReleaseVar(plugin_var); 210 EXPECT_EQ(0, deallocate_called); 211 212 // Synthesize an instance destuction, this should call Deallocate. 213 var_tracker().DidDeleteInstance(pp_instance); 214 EXPECT_EQ(1, deallocate_called); 215 } 216 217 // Tests what happens when a plugin keeps a ref to a plugin-implemented 218 // object var longer than the instance. We should not call the destructor until 219 // the plugin releases its last ref. 220 TEST_F(PluginVarTrackerTest, PluginObjectLeaked) { 221 ProxyAutoLock lock; 222 PP_Var host_object = MakeObject(12345); 223 PP_Instance pp_instance = 0x12345; 224 225 int deallocate_called = 0; 226 void* user_data = &deallocate_called; 227 228 // Make a var with one reference. 229 scoped_refptr<ProxyObjectVar> object( 230 new ProxyObjectVar(plugin_dispatcher(), host_object.value.as_id)); 231 PP_Var plugin_var = MakeObject(var_tracker().AddVar(object.get())); 232 var_tracker().PluginImplementedObjectCreated( 233 pp_instance, plugin_var, &mark_on_deallocate_class, user_data); 234 235 // Destroy the instance. This should not call deallocate since the plugin 236 // still has a ref. 237 var_tracker().DidDeleteInstance(pp_instance); 238 EXPECT_EQ(0, deallocate_called); 239 240 // Release the plugin ref to the var. Since the instance is gone this should 241 // call deallocate. 242 object = NULL; 243 var_tracker().ReleaseVar(plugin_var); 244 EXPECT_EQ(1, deallocate_called); 245 } 246 247 } // namespace proxy 248 } // namespace ppapi 249