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"
10 #include "ppapi/c/ppb_var.h"
11 #include "ppapi/cpp/module.h"
12 #include "ppapi/cpp/dev/scriptable_object_deprecated.h"
13 #include "ppapi/tests/testing_instance.h"
17 static const char kSetValueFunction
[] = "SetValue";
18 static const char kSetExceptionFunction
[] = "SetException";
19 static const char kReturnValueFunction
[] = "ReturnValue";
21 // ScriptableObject used by instance.
22 class InstanceSO
: public pp::deprecated::ScriptableObject
{
24 explicit InstanceSO(TestInstance
* i
);
25 virtual ~InstanceSO();
27 // pp::deprecated::ScriptableObject overrides.
28 bool HasMethod(const pp::Var
& name
, pp::Var
* exception
);
29 pp::Var
Call(const pp::Var
& name
,
30 const std::vector
<pp::Var
>& args
,
34 TestInstance
* test_instance_
;
35 // For out-of-process, the InstanceSO might be deleted after the instance was
36 // already destroyed, so we can't rely on test_instance_->testing_interface()
37 // being valid. Therefore we store our own.
38 const PPB_Testing_Private
* testing_interface_
;
41 InstanceSO::InstanceSO(TestInstance
* i
)
43 testing_interface_(i
->testing_interface()) {
44 // Set up a post-condition for the test so that we can ensure our destructor
45 // is called. This only works reliably in-process. Out-of-process, it only
46 // can work when the renderer stays alive a short while after the plugin
47 // instance is destroyed. If the renderer is being shut down, too much happens
48 // asynchronously for the out-of-process case to work reliably. In
50 // - The Var ReleaseObject message is asynchronous.
51 // - The PPB_Var_Deprecated host-side proxy posts a task to actually release
52 // the object when the ReleaseObject message is received.
53 // - The PPP_Class Deallocate message is asynchronous.
54 // At time of writing this comment, if you modify the code so that the above
55 // happens synchronously, and you remove the restriction that the plugin can't
56 // be unblocked by a sync message, then this check actually passes reliably
57 // for out-of-process. But we don't want to make any of those changes, so we
58 // just skip the check.
59 if (testing_interface_
->IsOutOfProcess() == PP_FALSE
) {
60 i
->instance()->AddPostCondition(
61 "window.document.getElementById('container').instance_object_destroyed"
66 InstanceSO::~InstanceSO() {
67 if (testing_interface_
->IsOutOfProcess() == PP_FALSE
) {
68 // TODO(dmichael): It would probably be best to make in-process consistent
69 // with out-of-process. That would mean that the instance
70 // would already be destroyed at this point.
71 pp::Var ret
= test_instance_
->instance()->ExecuteScript(
72 "document.getElementById('container').instance_object_destroyed=true;");
74 // Out-of-process, this destructor might not actually get invoked. See the
75 // comment in InstanceSO's constructor for an explanation. Also, instance()
76 // has already been destroyed :-(. So we can't really do anything here.
80 bool InstanceSO::HasMethod(const pp::Var
& name
, pp::Var
* exception
) {
81 if (!name
.is_string())
83 return name
.AsString() == kSetValueFunction
||
84 name
.AsString() == kSetExceptionFunction
||
85 name
.AsString() == kReturnValueFunction
;
88 pp::Var
InstanceSO::Call(const pp::Var
& method_name
,
89 const std::vector
<pp::Var
>& args
,
91 if (!method_name
.is_string())
93 std::string name
= method_name
.AsString();
95 if (name
== kSetValueFunction
) {
96 if (args
.size() != 1 || !args
[0].is_string())
97 *exception
= pp::Var("Bad argument to SetValue(<string>)");
99 test_instance_
->set_string(args
[0].AsString());
100 } else if (name
== kSetExceptionFunction
) {
101 if (args
.size() != 1 || !args
[0].is_string())
102 *exception
= pp::Var("Bad argument to SetException(<string>)");
104 *exception
= args
[0];
105 } else if (name
== kReturnValueFunction
) {
106 if (args
.size() != 1)
107 *exception
= pp::Var("Need single arg to call ReturnValue");
111 *exception
= pp::Var("Bad function call");
119 REGISTER_TEST_CASE(Instance
);
121 TestInstance::TestInstance(TestingInstance
* instance
) : TestCase(instance
) {
124 bool TestInstance::Init() {
128 TestInstance::~TestInstance() {
129 // Save the fact that we were destroyed in sessionStorage. This tests that
130 // we can ExecuteScript at instance destruction without crashing. It also
131 // allows us to check that ExecuteScript will run and succeed in certain
132 // cases. In particular, when the instance is destroyed by normal DOM
133 // deletion, ExecuteScript will actually work. See
134 // TestExecuteScriptInInstanceShutdown for that test. Note, however, that
135 // ExecuteScript will *not* have an effect when the instance is destroyed
136 // because the renderer was shut down.
137 pp::Var ret
= instance()->ExecuteScript(
138 "sessionStorage.setItem('instance_destroyed', 'true');");
141 void TestInstance::RunTests(const std::string
& filter
) {
142 RUN_TEST(ExecuteScript
, filter
);
143 RUN_TEST(RecursiveObjects
, filter
);
144 RUN_TEST(LeakedObjectDestructors
, filter
);
145 RUN_TEST(SetupExecuteScriptAtInstanceShutdown
, filter
);
146 RUN_TEST(ExecuteScriptAtInstanceShutdown
, filter
);
149 void TestInstance::LeakReferenceAndIgnore(const pp::Var
& leaked
) {
150 static const PPB_Var
* var_interface
= static_cast<const PPB_Var
*>(
151 pp::Module::Get()->GetBrowserInterface(PPB_VAR_INTERFACE
));
152 var_interface
->AddRef(leaked
.pp_var());
153 IgnoreLeakedVar(leaked
.pp_var().value
.as_id
);
156 pp::deprecated::ScriptableObject
* TestInstance::CreateTestObject() {
157 return new InstanceSO(this);
160 std::string
TestInstance::TestExecuteScript() {
161 // Simple call back into the plugin.
163 pp::Var ret
= instance_
->ExecuteScript(
164 "document.getElementById('plugin').SetValue('hello, world');",
166 ASSERT_TRUE(ret
.is_undefined());
167 ASSERT_TRUE(exception
.is_undefined());
168 ASSERT_TRUE(string_
== "hello, world");
170 // Return values from the plugin should be returned.
171 ret
= instance_
->ExecuteScript(
172 "document.getElementById('plugin').ReturnValue('return value');",
174 ASSERT_TRUE(ret
.is_string() && ret
.AsString() == "return value");
175 ASSERT_TRUE(exception
.is_undefined());
177 // Exception thrown by the plugin should be caught.
178 ret
= instance_
->ExecuteScript(
179 "document.getElementById('plugin').SetException('plugin exception');",
181 ASSERT_TRUE(ret
.is_undefined());
182 ASSERT_TRUE(exception
.is_string());
183 // Due to a limitation in the implementation of TryCatch, it doesn't actually
184 // pass the strings up. Since this is a trusted only interface, we've decided
185 // not to bother fixing this for now.
187 // Exception caused by string evaluation should be caught.
188 exception
= pp::Var();
189 ret
= instance_
->ExecuteScript("document.doesntExist()", &exception
);
190 ASSERT_TRUE(ret
.is_undefined());
191 ASSERT_TRUE(exception
.is_string()); // Don't know exactly what it will say.
196 // A scriptable object that contains other scriptable objects recursively. This
197 // is used to help verify that our scriptable object clean-up code works
199 class ObjectWithChildren
: public pp::deprecated::ScriptableObject
{
201 ObjectWithChildren(TestInstance
* i
, int num_descendents
) {
202 if (num_descendents
> 0) {
203 child_
= pp::VarPrivate(i
->instance(),
204 new ObjectWithChildren(i
, num_descendents
- 1));
207 struct IgnoreLeaks
{};
208 ObjectWithChildren(TestInstance
* i
, int num_descendents
, IgnoreLeaks
) {
209 if (num_descendents
> 0) {
210 child_
= pp::VarPrivate(i
->instance(),
211 new ObjectWithChildren(i
, num_descendents
- 1,
213 i
->IgnoreLeakedVar(child_
.pp_var().value
.as_id
);
217 pp::VarPrivate child_
;
220 std::string
TestInstance::TestRecursiveObjects() {
221 // These should be deleted when we exit scope, so should not leak.
222 pp::VarPrivate
not_leaked(instance(), new ObjectWithChildren(this, 50));
224 // Leak some, but tell TestCase to ignore the leaks. This test is run and then
225 // reloaded (see ppapi_uitest.cc). If these aren't cleaned up when the first
226 // run is torn down, they will show up as leaks in the second run.
227 // NOTE: The ScriptableObjects are actually leaked, but they should be removed
228 // from the tracker. See below for a test that verifies that the
229 // destructor is not run.
230 pp::VarPrivate
leaked(
232 new ObjectWithChildren(this, 50, ObjectWithChildren::IgnoreLeaks()));
233 // Now leak a reference to the root object. This should force the root and
234 // all its descendents to stay in the tracker.
235 LeakReferenceAndIgnore(leaked
);
240 // A scriptable object that should cause a crash if its destructor is run. We
241 // don't run the destructor for objects which the plugin leaks. This is to
242 // prevent them doing dangerous things at cleanup time, such as executing script
243 // or creating new objects.
244 class BadDestructorObject
: public pp::deprecated::ScriptableObject
{
246 BadDestructorObject() {}
247 ~BadDestructorObject() {
252 std::string
TestInstance::TestLeakedObjectDestructors() {
253 pp::VarPrivate
leaked(instance(), new BadDestructorObject());
254 // Leak a reference so it gets deleted on instance shutdown.
255 LeakReferenceAndIgnore(leaked
);
259 std::string
TestInstance::TestSetupExecuteScriptAtInstanceShutdown() {
260 // This test only exists so that it can be run before
261 // TestExecuteScriptAtInstanceShutdown. See the comment for that test.
263 pp::Var result
= instance()->ExecuteScript(
264 "sessionStorage.removeItem('instance_destroyed');", &exception
);
265 ASSERT_TRUE(exception
.is_undefined());
266 ASSERT_TRUE(result
.is_undefined());
270 std::string
TestInstance::TestExecuteScriptAtInstanceShutdown() {
271 // This test relies on the previous test being run in the same browser
272 // session, but in such a way that the instance is destroyed. See
273 // chrome/test/ppapi/ppapi_browsertest.cc for how the navigation happens.
275 // Given those constraints, ~TestInstance should have been invoked to set
276 // instance_destroyed in sessionStorage. So all we have to do is make sure
277 // that it was set as expected.
278 pp::Var result
= instance()->ExecuteScript(
279 "sessionStorage.getItem('instance_destroyed');");
280 ASSERT_TRUE(result
.is_string());
281 ASSERT_EQ(std::string("true"), result
.AsString());
282 instance()->ExecuteScript("sessionStorage.removeItem('instance_destroyed');");