Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / ppapi / tests / test_instance_deprecated.cc
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.
4
5 #include "ppapi/tests/test_instance_deprecated.h"
6
7 #include <assert.h>
8
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"
13
14 namespace {
15
16 static const char kSetValueFunction[] = "SetValue";
17 static const char kSetExceptionFunction[] = "SetException";
18 static const char kReturnValueFunction[] = "ReturnValue";
19
20 // ScriptableObject used by instance.
21 class InstanceSO : public pp::deprecated::ScriptableObject {
22  public:
23   explicit InstanceSO(TestInstance* i);
24   virtual ~InstanceSO();
25
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,
30                pp::Var* exception);
31
32  private:
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_;
38 };
39
40 InstanceSO::InstanceSO(TestInstance* i)
41     : test_instance_(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
48   // particular:
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"
61       );
62   }
63 }
64
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;");
72   } else {
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.
76   }
77 }
78
79 bool InstanceSO::HasMethod(const pp::Var& name, pp::Var* exception) {
80   if (!name.is_string())
81     return false;
82   return name.AsString() == kSetValueFunction ||
83          name.AsString() == kSetExceptionFunction ||
84          name.AsString() == kReturnValueFunction;
85 }
86
87 pp::Var InstanceSO::Call(const pp::Var& method_name,
88                          const std::vector<pp::Var>& args,
89                          pp::Var* exception) {
90   if (!method_name.is_string())
91     return false;
92   std::string name = method_name.AsString();
93
94   if (name == kSetValueFunction) {
95     if (args.size() != 1 || !args[0].is_string())
96       *exception = pp::Var("Bad argument to SetValue(<string>)");
97     else
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>)");
102     else
103       *exception = args[0];
104   } else if (name == kReturnValueFunction) {
105     if (args.size() != 1)
106       *exception = pp::Var("Need single arg to call ReturnValue");
107     else
108       return args[0];
109   } else {
110     *exception = pp::Var("Bad function call");
111   }
112
113   return pp::Var();
114 }
115
116 }  // namespace
117
118 REGISTER_TEST_CASE(Instance);
119
120 TestInstance::TestInstance(TestingInstance* instance) : TestCase(instance) {
121 }
122
123 bool TestInstance::Init() {
124   return true;
125 }
126
127 TestInstance::~TestInstance() {
128   ResetTestObject();
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');");
144 }
145
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);
152 }
153
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);
159 }
160
161 pp::deprecated::ScriptableObject* TestInstance::CreateTestObject() {
162   return new InstanceSO(this);
163 }
164
165 std::string TestInstance::TestExecuteScript() {
166   // Simple call back into the plugin.
167   pp::Var exception;
168   pp::Var ret = instance_->ExecuteScript(
169       "document.getElementById('plugin').SetValue('hello, world');",
170       &exception);
171   ASSERT_TRUE(ret.is_undefined());
172   ASSERT_TRUE(exception.is_undefined());
173   ASSERT_TRUE(string_ == "hello, world");
174
175   // Return values from the plugin should be returned.
176   ret = instance_->ExecuteScript(
177       "document.getElementById('plugin').ReturnValue('return value');",
178       &exception);
179   ASSERT_TRUE(ret.is_string() && ret.AsString() == "return value");
180   ASSERT_TRUE(exception.is_undefined());
181
182   // Exception thrown by the plugin should be caught.
183   ret = instance_->ExecuteScript(
184       "document.getElementById('plugin').SetException('plugin exception');",
185       &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.
191
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.
197
198   PASS();
199 }
200
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
203 // properly.
204 class ObjectWithChildren : public pp::deprecated::ScriptableObject {
205  public:
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));
210     }
211   }
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,
217                                                      IgnoreLeaks()));
218       i->IgnoreLeakedVar(child_.pp_var().value.as_id);
219     }
220   }
221  private:
222   pp::VarPrivate child_;
223 };
224
225 std::string TestInstance::TestRecursiveObjects() {
226   const int kNumChildren = 20;
227   {
228     // These should be deleted when we exit scope, so should not leak.
229     pp::VarPrivate not_leaked(instance(), new ObjectWithChildren(this,
230                                                                  kNumChildren));
231   }
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());
237
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(
245       instance(),
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);
251
252   PASS();
253 }
254
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 {
260  public:
261   BadDestructorObject() {}
262   ~BadDestructorObject() {
263     assert(false);
264   }
265 };
266
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);
271   PASS();
272 }
273
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.
277   pp::Var exception;
278   pp::Var result = instance()->ExecuteScript(
279       "sessionStorage.removeItem('instance_destroyed');", &exception);
280   ASSERT_TRUE(exception.is_undefined());
281   ASSERT_TRUE(result.is_undefined());
282   PASS();
283 }
284
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.
289   //
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');");
298
299   PASS();
300 }
301