Roll src/third_party/WebKit 28cb7a5:eda0012 (svn 190243:190250)
[chromium-blink-merge.git] / ppapi / tests / test_instance_deprecated.cc
blobe4235b84878451e95550095de6a51e73fd6383cb
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"
7 #include <assert.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)
18 : test_instance_(i),
19 testing_interface_(i->testing_interface()) {
22 InstanceSO::~InstanceSO() {
23 if (test_instance_)
24 test_instance_->clear_instance_so();
27 bool InstanceSO::HasMethod(const pp::Var& name, pp::Var* exception) {
28 if (!name.is_string())
29 return false;
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,
37 pp::Var* exception) {
38 if (!method_name.is_string())
39 return false;
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>)");
50 else
51 *exception = args[0];
52 } else if (name == kReturnValueFunction) {
53 if (args.size() != 1)
54 *exception = pp::Var("Need single arg to call ReturnValue");
55 else
56 return args[0];
57 } else {
58 *exception = pp::Var("Bad function call");
61 return pp::Var();
64 REGISTER_TEST_CASE(Instance);
66 TestInstance::TestInstance(TestingInstance* instance)
67 : TestCase(instance),
68 instance_so_(NULL) {}
70 bool TestInstance::Init() {
71 return true;
74 TestInstance::~TestInstance() {
75 ResetTestObject();
76 if (testing_interface_->IsOutOfProcess() == PP_FALSE) {
77 // This should cause the instance object's descructor 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
85 // particular:
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_);
96 } else {
97 // Out-of-process, this destructor might not actually get invoked. Clear
98 // the InstanceSOs reference to the instance so there is no UAF.
99 if (instance_so_)
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() {
131 if (!instance_so_)
132 instance_so_ = new InstanceSO(this);
133 return instance_so_;
136 std::string TestInstance::TestExecuteScript() {
137 // Simple call back into the plugin.
138 pp::Var exception;
139 pp::Var ret = instance_->ExecuteScript(
140 "document.getElementById('plugin').SetValue('hello, world');",
141 &exception);
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');",
149 &exception);
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');",
156 &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.
169 PASS();
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
174 // properly.
175 class ObjectWithChildren : public pp::deprecated::ScriptableObject {
176 public:
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,
188 IgnoreLeaks()));
189 i->IgnoreLeakedVar(child_.pp_var().value.as_id);
192 private:
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,
201 kNumChildren));
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(
216 instance(),
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);
223 PASS();
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 {
231 public:
232 BadDestructorObject() {}
233 ~BadDestructorObject() {
234 assert(false);
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);
242 PASS();
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.
248 pp::Var exception;
249 pp::Var result = instance()->ExecuteScript(
250 "sessionStorage.removeItem('instance_destroyed');", &exception);
251 ASSERT_TRUE(exception.is_undefined());
252 ASSERT_TRUE(result.is_undefined());
253 PASS();
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');");
270 PASS();