Web MIDI: MidiManager crashes if a session is ended while initializing
[chromium-blink-merge.git] / ppapi / tests / test_instance_deprecated.cc
blob1fd99e15b0f9813977a77100de4709ab229f5cd5
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>
8 #include <iostream>
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"
15 namespace {
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 {
23 public:
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,
31 pp::Var* exception);
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_Private* testing_interface_;
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"
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.
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;
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();
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");
114 return pp::Var();
117 } // namespace
119 REGISTER_TEST_CASE(Instance);
121 TestInstance::TestInstance(TestingInstance* instance) : TestCase(instance) {
124 bool TestInstance::Init() {
125 return true;
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.
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");
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());
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.
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.
193 PASS();
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));
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);
216 private:
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(
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);
237 PASS();
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);
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();
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();
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');");
284 PASS();