- add sources.
[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 #include <iostream>
9
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"
14
15 namespace {
16
17 static const char kSetValueFunction[] = "SetValue";
18 static const char kSetExceptionFunction[] = "SetException";
19 static const char kReturnValueFunction[] = "ReturnValue";
20
21 // ScriptableObject used by instance.
22 class InstanceSO : public pp::deprecated::ScriptableObject {
23  public:
24   explicit InstanceSO(TestInstance* i);
25   virtual ~InstanceSO();
26
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,
31                pp::Var* exception);
32
33  private:
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_Dev* testing_interface_;
39 };
40
41 InstanceSO::InstanceSO(TestInstance* i)
42     : test_instance_(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
49   // particular:
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"
62       );
63   }
64 }
65
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;");
73   } else {
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.
77   }
78 }
79
80 bool InstanceSO::HasMethod(const pp::Var& name, pp::Var* exception) {
81   if (!name.is_string())
82     return false;
83   return name.AsString() == kSetValueFunction ||
84          name.AsString() == kSetExceptionFunction ||
85          name.AsString() == kReturnValueFunction;
86 }
87
88 pp::Var InstanceSO::Call(const pp::Var& method_name,
89                          const std::vector<pp::Var>& args,
90                          pp::Var* exception) {
91   if (!method_name.is_string())
92     return false;
93   std::string name = method_name.AsString();
94
95   if (name == kSetValueFunction) {
96     if (args.size() != 1 || !args[0].is_string())
97       *exception = pp::Var("Bad argument to SetValue(<string>)");
98     else
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>)");
103     else
104       *exception = args[0];
105   } else if (name == kReturnValueFunction) {
106     if (args.size() != 1)
107       *exception = pp::Var("Need single arg to call ReturnValue");
108     else
109       return args[0];
110   } else {
111     *exception = pp::Var("Bad function call");
112   }
113
114   return pp::Var();
115 }
116
117 }  // namespace
118
119 REGISTER_TEST_CASE(Instance);
120
121 TestInstance::TestInstance(TestingInstance* instance) : TestCase(instance) {
122 }
123
124 bool TestInstance::Init() {
125   return true;
126 }
127
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');");
139 }
140
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);
147 }
148
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);
154 }
155
156 pp::deprecated::ScriptableObject* TestInstance::CreateTestObject() {
157   return new InstanceSO(this);
158 }
159
160 std::string TestInstance::TestExecuteScript() {
161   // Simple call back into the plugin.
162   pp::Var exception;
163   pp::Var ret = instance_->ExecuteScript(
164       "document.getElementById('plugin').SetValue('hello, world');",
165       &exception);
166   ASSERT_TRUE(ret.is_undefined());
167   ASSERT_TRUE(exception.is_undefined());
168   ASSERT_TRUE(string_ == "hello, world");
169
170   // Return values from the plugin should be returned.
171   ret = instance_->ExecuteScript(
172       "document.getElementById('plugin').ReturnValue('return value');",
173       &exception);
174   ASSERT_TRUE(ret.is_string() && ret.AsString() == "return value");
175   ASSERT_TRUE(exception.is_undefined());
176
177   // Exception thrown by the plugin should be caught.
178   ret = instance_->ExecuteScript(
179       "document.getElementById('plugin').SetException('plugin exception');",
180       &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.
186
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.
192
193   PASS();
194 }
195
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
198 // properly.
199 class ObjectWithChildren : public pp::deprecated::ScriptableObject {
200  public:
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));
205     }
206   }
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,
212                                                      IgnoreLeaks()));
213       i->IgnoreLeakedVar(child_.pp_var().value.as_id);
214     }
215   }
216  private:
217   pp::VarPrivate child_;
218 };
219
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));
223
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(
231       instance(),
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);
236
237   PASS();
238 }
239
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 {
245  public:
246   BadDestructorObject() {}
247   ~BadDestructorObject() {
248     assert(false);
249   }
250 };
251
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);
256   PASS();
257 }
258
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.
262   pp::Var exception;
263   pp::Var result = instance()->ExecuteScript(
264       "sessionStorage.removeItem('instance_destroyed');", &exception);
265   ASSERT_TRUE(exception.is_undefined());
266   ASSERT_TRUE(result.is_undefined());
267   PASS();
268 }
269
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.
274   //
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');");
283
284   PASS();
285 }
286