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