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.
5 #include "ppapi/tests/test_instance_deprecated.h"
9 #include "ppapi/c/ppb_var.h"
10 #include "ppapi/cpp/module.h"
11 #include "ppapi/tests/testing_instance.h"
13 static const char kSetValueFunction
[] = "SetValue";
14 static const char kSetExceptionFunction
[] = "SetException";
15 static const char kReturnValueFunction
[] = "ReturnValue";
17 InstanceSO::InstanceSO(TestInstance
* i
)
19 testing_interface_(i
->testing_interface()) {
22 InstanceSO::~InstanceSO() {
24 test_instance_
->clear_instance_so();
27 bool InstanceSO::HasMethod(const pp::Var
& name
, pp::Var
* exception
) {
28 if (!name
.is_string())
30 return name
.AsString() == kSetValueFunction
||
31 name
.AsString() == kSetExceptionFunction
||
32 name
.AsString() == kReturnValueFunction
;
35 pp::Var
InstanceSO::Call(const pp::Var
& method_name
,
36 const std::vector
<pp::Var
>& args
,
38 if (!method_name
.is_string())
40 std::string name
= method_name
.AsString();
42 if (name
== kSetValueFunction
) {
43 if (args
.size() != 1 || !args
[0].is_string())
44 *exception
= pp::Var("Bad argument to SetValue(<string>)");
45 else if (test_instance_
)
46 test_instance_
->set_string(args
[0].AsString());
47 } else if (name
== kSetExceptionFunction
) {
48 if (args
.size() != 1 || !args
[0].is_string())
49 *exception
= pp::Var("Bad argument to SetException(<string>)");
52 } else if (name
== kReturnValueFunction
) {
54 *exception
= pp::Var("Need single arg to call ReturnValue");
58 *exception
= pp::Var("Bad function call");
64 REGISTER_TEST_CASE(Instance
);
66 TestInstance::TestInstance(TestingInstance
* instance
)
70 bool TestInstance::Init() {
74 TestInstance::~TestInstance() {
76 if (testing_interface_
->IsOutOfProcess() == PP_FALSE
) {
77 // This should cause the instance object's destructor to be called.
78 testing_interface_
->RunV8GC(instance_
->pp_instance());
80 // Test a post-condition which ensures the instance objects destructor is
81 // called. This only works reliably in-process. Out-of-process, it only
82 // can work when the renderer stays alive a short while after the plugin
83 // instance is destroyed. If the renderer is being shut down, too much
84 // happens asynchronously for the out-of-process case to work reliably. In
86 // - The Var ReleaseObject message is asynchronous.
87 // - The PPB_Var_Deprecated host-side proxy posts a task to actually
88 // release the object when the ReleaseObject message is received.
89 // - The PPP_Class Deallocate message is asynchronous.
90 // At time of writing this comment, if you modify the code so that the above
91 // happens synchronously, and you remove the restriction that the plugin
92 // can't be unblocked by a sync message, then this check actually passes
93 // reliably for out-of-process. But we don't want to make any of those
94 // changes so we just skip the check.
95 PP_DCHECK(!instance_so_
);
97 // Out-of-process, this destructor might not actually get invoked. Clear
98 // the InstanceSOs reference to the instance so there is no UAF.
100 instance_so_
->clear_test_instance();
103 // Save the fact that we were destroyed in sessionStorage. This tests that
104 // we can ExecuteScript at instance destruction without crashing. It also
105 // allows us to check that ExecuteScript will run and succeed in certain
106 // cases. In particular, when the instance is destroyed by normal DOM
107 // deletion, ExecuteScript will actually work. See
108 // TestExecuteScriptInInstanceShutdown for that test. Note, however, that
109 // ExecuteScript will *not* have an effect when the instance is destroyed
110 // because the renderer was shut down.
111 pp::Var ret
= instance()->ExecuteScript(
112 "sessionStorage.setItem('instance_destroyed', 'true');");
115 void TestInstance::RunTests(const std::string
& filter
) {
116 RUN_TEST(ExecuteScript
, filter
);
117 RUN_TEST(RecursiveObjects
, filter
);
118 RUN_TEST(LeakedObjectDestructors
, filter
);
119 RUN_TEST(SetupExecuteScriptAtInstanceShutdown
, filter
);
120 RUN_TEST(ExecuteScriptAtInstanceShutdown
, filter
);
123 void TestInstance::LeakReferenceAndIgnore(const pp::Var
& leaked
) {
124 static const PPB_Var
* var_interface
= static_cast<const PPB_Var
*>(
125 pp::Module::Get()->GetBrowserInterface(PPB_VAR_INTERFACE
));
126 var_interface
->AddRef(leaked
.pp_var());
127 IgnoreLeakedVar(leaked
.pp_var().value
.as_id
);
130 pp::deprecated::ScriptableObject
* TestInstance::CreateTestObject() {
132 instance_so_
= new InstanceSO(this);
136 std::string
TestInstance::TestExecuteScript() {
137 // Simple call back into the plugin.
139 pp::Var ret
= instance_
->ExecuteScript(
140 "document.getElementById('plugin').SetValue('hello, world');",
142 ASSERT_TRUE(ret
.is_undefined());
143 ASSERT_TRUE(exception
.is_undefined());
144 ASSERT_TRUE(string_
== "hello, world");
146 // Return values from the plugin should be returned.
147 ret
= instance_
->ExecuteScript(
148 "document.getElementById('plugin').ReturnValue('return value');",
150 ASSERT_TRUE(ret
.is_string() && ret
.AsString() == "return value");
151 ASSERT_TRUE(exception
.is_undefined());
153 // Exception thrown by the plugin should be caught.
154 ret
= instance_
->ExecuteScript(
155 "document.getElementById('plugin').SetException('plugin exception');",
157 ASSERT_TRUE(ret
.is_undefined());
158 ASSERT_TRUE(exception
.is_string());
159 // Due to a limitation in the implementation of TryCatch, it doesn't actually
160 // pass the strings up. Since this is a trusted only interface, we've decided
161 // not to bother fixing this for now.
163 // Exception caused by string evaluation should be caught.
164 exception
= pp::Var();
165 ret
= instance_
->ExecuteScript("document.doesntExist()", &exception
);
166 ASSERT_TRUE(ret
.is_undefined());
167 ASSERT_TRUE(exception
.is_string()); // Don't know exactly what it will say.
172 // A scriptable object that contains other scriptable objects recursively. This
173 // is used to help verify that our scriptable object clean-up code works
175 class ObjectWithChildren
: public pp::deprecated::ScriptableObject
{
177 ObjectWithChildren(TestInstance
* i
, int num_descendents
) {
178 if (num_descendents
> 0) {
179 child_
= pp::VarPrivate(i
->instance(),
180 new ObjectWithChildren(i
, num_descendents
- 1));
183 struct IgnoreLeaks
{};
184 ObjectWithChildren(TestInstance
* i
, int num_descendents
, IgnoreLeaks
) {
185 if (num_descendents
> 0) {
186 child_
= pp::VarPrivate(i
->instance(),
187 new ObjectWithChildren(i
, num_descendents
- 1,
189 i
->IgnoreLeakedVar(child_
.pp_var().value
.as_id
);
193 pp::VarPrivate child_
;
196 std::string
TestInstance::TestRecursiveObjects() {
197 const int kNumChildren
= 20;
199 // These should be deleted when we exit scope, so should not leak.
200 pp::VarPrivate
not_leaked(instance(), new ObjectWithChildren(this,
203 // We need to run the GC multiple times until all of the vars are released.
204 // Each GC invocation will result in releasing a var, which will result in its
205 // children not having any references, allowing them also to be collected.
206 for (int i
= 0; i
< kNumChildren
; ++i
)
207 testing_interface_
->RunV8GC(instance_
->pp_instance());
209 // Leak some, but tell TestCase to ignore the leaks. This test is run and then
210 // reloaded (see ppapi_uitest.cc). If these aren't cleaned up when the first
211 // run is torn down, they will show up as leaks in the second run.
212 // NOTE: The ScriptableObjects are actually leaked, but they should be removed
213 // from the tracker. See below for a test that verifies that the
214 // destructor is not run.
215 pp::VarPrivate
leaked(
217 new ObjectWithChildren(this, kNumChildren
,
218 ObjectWithChildren::IgnoreLeaks()));
219 // Now leak a reference to the root object. This should force the root and
220 // all its descendents to stay in the tracker.
221 LeakReferenceAndIgnore(leaked
);
226 // A scriptable object that should cause a crash if its destructor is run. We
227 // don't run the destructor for objects which the plugin leaks. This is to
228 // prevent them doing dangerous things at cleanup time, such as executing script
229 // or creating new objects.
230 class BadDestructorObject
: public pp::deprecated::ScriptableObject
{
232 BadDestructorObject() {}
233 ~BadDestructorObject() {
238 std::string
TestInstance::TestLeakedObjectDestructors() {
239 pp::VarPrivate
leaked(instance(), new BadDestructorObject());
240 // Leak a reference so it gets deleted on instance shutdown.
241 LeakReferenceAndIgnore(leaked
);
245 std::string
TestInstance::TestSetupExecuteScriptAtInstanceShutdown() {
246 // This test only exists so that it can be run before
247 // TestExecuteScriptAtInstanceShutdown. See the comment for that test.
249 pp::Var result
= instance()->ExecuteScript(
250 "sessionStorage.removeItem('instance_destroyed');", &exception
);
251 ASSERT_TRUE(exception
.is_undefined());
252 ASSERT_TRUE(result
.is_undefined());
256 std::string
TestInstance::TestExecuteScriptAtInstanceShutdown() {
257 // This test relies on the previous test being run in the same browser
258 // session, but in such a way that the instance is destroyed. See
259 // chrome/test/ppapi/ppapi_browsertest.cc for how the navigation happens.
261 // Given those constraints, ~TestInstance should have been invoked to set
262 // instance_destroyed in sessionStorage. So all we have to do is make sure
263 // that it was set as expected.
264 pp::Var result
= instance()->ExecuteScript(
265 "sessionStorage.getItem('instance_destroyed');");
266 ASSERT_TRUE(result
.is_string());
267 ASSERT_EQ(std::string("true"), result
.AsString());
268 instance()->ExecuteScript("sessionStorage.removeItem('instance_destroyed');");