1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
|
// 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/tests/test_instance_deprecated.h"
#include <assert.h>
#include "ppapi/c/ppb_var.h"
#include "ppapi/cpp/module.h"
#include "ppapi/cpp/dev/scriptable_object_deprecated.h"
#include "ppapi/tests/testing_instance.h"
namespace {
static const char kSetValueFunction[] = "SetValue";
static const char kSetExceptionFunction[] = "SetException";
static const char kReturnValueFunction[] = "ReturnValue";
// ScriptableObject used by instance.
class InstanceSO : public pp::deprecated::ScriptableObject {
public:
explicit InstanceSO(TestInstance* i);
virtual ~InstanceSO();
// pp::deprecated::ScriptableObject overrides.
bool HasMethod(const pp::Var& name, pp::Var* exception);
pp::Var Call(const pp::Var& name,
const std::vector<pp::Var>& args,
pp::Var* exception);
private:
TestInstance* test_instance_;
// For out-of-process, the InstanceSO might be deleted after the instance was
// already destroyed, so we can't rely on test_instance_->testing_interface()
// being valid. Therefore we store our own.
const PPB_Testing_Private* testing_interface_;
};
InstanceSO::InstanceSO(TestInstance* i)
: test_instance_(i),
testing_interface_(i->testing_interface()) {
if (testing_interface_->IsOutOfProcess() == PP_FALSE)
test_instance_->set_instance_object_destroyed(false);
}
InstanceSO::~InstanceSO() {
test_instance_->set_instance_object_destroyed(true);
}
bool InstanceSO::HasMethod(const pp::Var& name, pp::Var* exception) {
if (!name.is_string())
return false;
return name.AsString() == kSetValueFunction ||
name.AsString() == kSetExceptionFunction ||
name.AsString() == kReturnValueFunction;
}
pp::Var InstanceSO::Call(const pp::Var& method_name,
const std::vector<pp::Var>& args,
pp::Var* exception) {
if (!method_name.is_string())
return false;
std::string name = method_name.AsString();
if (name == kSetValueFunction) {
if (args.size() != 1 || !args[0].is_string())
*exception = pp::Var("Bad argument to SetValue(<string>)");
else
test_instance_->set_string(args[0].AsString());
} else if (name == kSetExceptionFunction) {
if (args.size() != 1 || !args[0].is_string())
*exception = pp::Var("Bad argument to SetException(<string>)");
else
*exception = args[0];
} else if (name == kReturnValueFunction) {
if (args.size() != 1)
*exception = pp::Var("Need single arg to call ReturnValue");
else
return args[0];
} else {
*exception = pp::Var("Bad function call");
}
return pp::Var();
}
} // namespace
REGISTER_TEST_CASE(Instance);
TestInstance::TestInstance(TestingInstance* instance)
: TestCase(instance),
instance_object_destroyed_(false) {}
bool TestInstance::Init() {
return true;
}
TestInstance::~TestInstance() {
ResetTestObject();
if (testing_interface_->IsOutOfProcess() == PP_FALSE) {
// This should cause the instance objects descructor to be called.
testing_interface_->RunV8GC(instance_->pp_instance());
// Test a post-condition which ensures the instance objects destructor is
// called. This only works reliably in-process. Out-of-process, it only
// can work when the renderer stays alive a short while after the plugin
// instance is destroyed. If the renderer is being shut down, too much
// happens asynchronously for the out-of-process case to work reliably. In
// particular:
// - The Var ReleaseObject message is asynchronous.
// - The PPB_Var_Deprecated host-side proxy posts a task to actually
// release the object when the ReleaseObject message is received.
// - The PPP_Class Deallocate message is asynchronous.
// At time of writing this comment, if you modify the code so that the above
// happens synchronously, and you remove the restriction that the plugin
// can't be unblocked by a sync message, then this check actually passes
// reliably for out-of-process. But we don't want to make any of those
// changes so we just skip the check.
PP_DCHECK(instance_object_destroyed_);
} else {
// Out-of-process, this destructor might not actually get invoked. See the
// comment in InstanceSO's constructor for an explanation. Also, instance()
// has already been destroyed :-(. So we can't really do anything here.
}
// Save the fact that we were destroyed in sessionStorage. This tests that
// we can ExecuteScript at instance destruction without crashing. It also
// allows us to check that ExecuteScript will run and succeed in certain
// cases. In particular, when the instance is destroyed by normal DOM
// deletion, ExecuteScript will actually work. See
// TestExecuteScriptInInstanceShutdown for that test. Note, however, that
// ExecuteScript will *not* have an effect when the instance is destroyed
// because the renderer was shut down.
pp::Var ret = instance()->ExecuteScript(
"sessionStorage.setItem('instance_destroyed', 'true');");
}
void TestInstance::RunTests(const std::string& filter) {
RUN_TEST(ExecuteScript, filter);
RUN_TEST(RecursiveObjects, filter);
RUN_TEST(LeakedObjectDestructors, filter);
RUN_TEST(SetupExecuteScriptAtInstanceShutdown, filter);
RUN_TEST(ExecuteScriptAtInstanceShutdown, filter);
}
void TestInstance::LeakReferenceAndIgnore(const pp::Var& leaked) {
static const PPB_Var* var_interface = static_cast<const PPB_Var*>(
pp::Module::Get()->GetBrowserInterface(PPB_VAR_INTERFACE));
var_interface->AddRef(leaked.pp_var());
IgnoreLeakedVar(leaked.pp_var().value.as_id);
}
pp::deprecated::ScriptableObject* TestInstance::CreateTestObject() {
return new InstanceSO(this);
}
std::string TestInstance::TestExecuteScript() {
// Simple call back into the plugin.
pp::Var exception;
pp::Var ret = instance_->ExecuteScript(
"document.getElementById('plugin').SetValue('hello, world');",
&exception);
ASSERT_TRUE(ret.is_undefined());
ASSERT_TRUE(exception.is_undefined());
ASSERT_TRUE(string_ == "hello, world");
// Return values from the plugin should be returned.
ret = instance_->ExecuteScript(
"document.getElementById('plugin').ReturnValue('return value');",
&exception);
ASSERT_TRUE(ret.is_string() && ret.AsString() == "return value");
ASSERT_TRUE(exception.is_undefined());
// Exception thrown by the plugin should be caught.
ret = instance_->ExecuteScript(
"document.getElementById('plugin').SetException('plugin exception');",
&exception);
ASSERT_TRUE(ret.is_undefined());
ASSERT_TRUE(exception.is_string());
// Due to a limitation in the implementation of TryCatch, it doesn't actually
// pass the strings up. Since this is a trusted only interface, we've decided
// not to bother fixing this for now.
// Exception caused by string evaluation should be caught.
exception = pp::Var();
ret = instance_->ExecuteScript("document.doesntExist()", &exception);
ASSERT_TRUE(ret.is_undefined());
ASSERT_TRUE(exception.is_string()); // Don't know exactly what it will say.
PASS();
}
// A scriptable object that contains other scriptable objects recursively. This
// is used to help verify that our scriptable object clean-up code works
// properly.
class ObjectWithChildren : public pp::deprecated::ScriptableObject {
public:
ObjectWithChildren(TestInstance* i, int num_descendents) {
if (num_descendents > 0) {
child_ = pp::VarPrivate(i->instance(),
new ObjectWithChildren(i, num_descendents - 1));
}
}
struct IgnoreLeaks {};
ObjectWithChildren(TestInstance* i, int num_descendents, IgnoreLeaks) {
if (num_descendents > 0) {
child_ = pp::VarPrivate(i->instance(),
new ObjectWithChildren(i, num_descendents - 1,
IgnoreLeaks()));
i->IgnoreLeakedVar(child_.pp_var().value.as_id);
}
}
private:
pp::VarPrivate child_;
};
std::string TestInstance::TestRecursiveObjects() {
const int kNumChildren = 20;
{
// These should be deleted when we exit scope, so should not leak.
pp::VarPrivate not_leaked(instance(), new ObjectWithChildren(this,
kNumChildren));
}
// We need to run the GC multiple times until all of the vars are released.
// Each GC invocation will result in releasing a var, which will result in its
// children not having any references, allowing them also to be collected.
for (int i = 0; i < kNumChildren; ++i)
testing_interface_->RunV8GC(instance_->pp_instance());
// Leak some, but tell TestCase to ignore the leaks. This test is run and then
// reloaded (see ppapi_uitest.cc). If these aren't cleaned up when the first
// run is torn down, they will show up as leaks in the second run.
// NOTE: The ScriptableObjects are actually leaked, but they should be removed
// from the tracker. See below for a test that verifies that the
// destructor is not run.
pp::VarPrivate leaked(
instance(),
new ObjectWithChildren(this, kNumChildren,
ObjectWithChildren::IgnoreLeaks()));
// Now leak a reference to the root object. This should force the root and
// all its descendents to stay in the tracker.
LeakReferenceAndIgnore(leaked);
PASS();
}
// A scriptable object that should cause a crash if its destructor is run. We
// don't run the destructor for objects which the plugin leaks. This is to
// prevent them doing dangerous things at cleanup time, such as executing script
// or creating new objects.
class BadDestructorObject : public pp::deprecated::ScriptableObject {
public:
BadDestructorObject() {}
~BadDestructorObject() {
assert(false);
}
};
std::string TestInstance::TestLeakedObjectDestructors() {
pp::VarPrivate leaked(instance(), new BadDestructorObject());
// Leak a reference so it gets deleted on instance shutdown.
LeakReferenceAndIgnore(leaked);
PASS();
}
std::string TestInstance::TestSetupExecuteScriptAtInstanceShutdown() {
// This test only exists so that it can be run before
// TestExecuteScriptAtInstanceShutdown. See the comment for that test.
pp::Var exception;
pp::Var result = instance()->ExecuteScript(
"sessionStorage.removeItem('instance_destroyed');", &exception);
ASSERT_TRUE(exception.is_undefined());
ASSERT_TRUE(result.is_undefined());
PASS();
}
std::string TestInstance::TestExecuteScriptAtInstanceShutdown() {
// This test relies on the previous test being run in the same browser
// session, but in such a way that the instance is destroyed. See
// chrome/test/ppapi/ppapi_browsertest.cc for how the navigation happens.
//
// Given those constraints, ~TestInstance should have been invoked to set
// instance_destroyed in sessionStorage. So all we have to do is make sure
// that it was set as expected.
pp::Var result = instance()->ExecuteScript(
"sessionStorage.getItem('instance_destroyed');");
ASSERT_TRUE(result.is_string());
ASSERT_EQ(std::string("true"), result.AsString());
instance()->ExecuteScript("sessionStorage.removeItem('instance_destroyed');");
PASS();
}
|