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
13namespace ppapi {
14namespace proxy {
15
16namespace {
17
18PP_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.
27void MarkOnDeallocate(void* object) {
28  (*static_cast<int*>(object))++;
29}
30
31// A class that just implements MarkOnDeallocate on destruction.
32PPP_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
46class 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
65TEST_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
80TEST_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".
116TEST_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
164TEST_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.
191TEST_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.
220TEST_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