OLD | NEW |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "ppapi/tests/test_instance_deprecated.h" | 5 #include "ppapi/tests/test_instance_deprecated.h" |
6 | 6 |
7 #include <assert.h> | 7 #include <assert.h> |
8 | 8 |
9 #include "ppapi/c/ppb_var.h" | 9 #include "ppapi/c/ppb_var.h" |
10 #include "ppapi/cpp/module.h" | 10 #include "ppapi/cpp/module.h" |
11 #include "ppapi/cpp/dev/scriptable_object_deprecated.h" | |
12 #include "ppapi/tests/testing_instance.h" | 11 #include "ppapi/tests/testing_instance.h" |
13 | 12 |
14 namespace { | |
15 | |
16 static const char kSetValueFunction[] = "SetValue"; | 13 static const char kSetValueFunction[] = "SetValue"; |
17 static const char kSetExceptionFunction[] = "SetException"; | 14 static const char kSetExceptionFunction[] = "SetException"; |
18 static const char kReturnValueFunction[] = "ReturnValue"; | 15 static const char kReturnValueFunction[] = "ReturnValue"; |
19 | 16 |
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) | 17 InstanceSO::InstanceSO(TestInstance* i) |
41 : test_instance_(i), | 18 : test_instance_(i), |
42 testing_interface_(i->testing_interface()) { | 19 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 } | 20 } |
64 | 21 |
65 InstanceSO::~InstanceSO() { | 22 InstanceSO::~InstanceSO() { |
66 if (testing_interface_->IsOutOfProcess() == PP_FALSE) { | 23 if (test_instance_) |
67 // TODO(dmichael): It would probably be best to make in-process consistent | 24 test_instance_->clear_instance_so(); |
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 } | 25 } |
78 | 26 |
79 bool InstanceSO::HasMethod(const pp::Var& name, pp::Var* exception) { | 27 bool InstanceSO::HasMethod(const pp::Var& name, pp::Var* exception) { |
80 if (!name.is_string()) | 28 if (!name.is_string()) |
81 return false; | 29 return false; |
82 return name.AsString() == kSetValueFunction || | 30 return name.AsString() == kSetValueFunction || |
83 name.AsString() == kSetExceptionFunction || | 31 name.AsString() == kSetExceptionFunction || |
84 name.AsString() == kReturnValueFunction; | 32 name.AsString() == kReturnValueFunction; |
85 } | 33 } |
86 | 34 |
87 pp::Var InstanceSO::Call(const pp::Var& method_name, | 35 pp::Var InstanceSO::Call(const pp::Var& method_name, |
88 const std::vector<pp::Var>& args, | 36 const std::vector<pp::Var>& args, |
89 pp::Var* exception) { | 37 pp::Var* exception) { |
90 if (!method_name.is_string()) | 38 if (!method_name.is_string()) |
91 return false; | 39 return false; |
92 std::string name = method_name.AsString(); | 40 std::string name = method_name.AsString(); |
93 | 41 |
94 if (name == kSetValueFunction) { | 42 if (name == kSetValueFunction) { |
95 if (args.size() != 1 || !args[0].is_string()) | 43 if (args.size() != 1 || !args[0].is_string()) |
96 *exception = pp::Var("Bad argument to SetValue(<string>)"); | 44 *exception = pp::Var("Bad argument to SetValue(<string>)"); |
97 else | 45 else if (test_instance_) |
98 test_instance_->set_string(args[0].AsString()); | 46 test_instance_->set_string(args[0].AsString()); |
99 } else if (name == kSetExceptionFunction) { | 47 } else if (name == kSetExceptionFunction) { |
100 if (args.size() != 1 || !args[0].is_string()) | 48 if (args.size() != 1 || !args[0].is_string()) |
101 *exception = pp::Var("Bad argument to SetException(<string>)"); | 49 *exception = pp::Var("Bad argument to SetException(<string>)"); |
102 else | 50 else |
103 *exception = args[0]; | 51 *exception = args[0]; |
104 } else if (name == kReturnValueFunction) { | 52 } else if (name == kReturnValueFunction) { |
105 if (args.size() != 1) | 53 if (args.size() != 1) |
106 *exception = pp::Var("Need single arg to call ReturnValue"); | 54 *exception = pp::Var("Need single arg to call ReturnValue"); |
107 else | 55 else |
108 return args[0]; | 56 return args[0]; |
109 } else { | 57 } else { |
110 *exception = pp::Var("Bad function call"); | 58 *exception = pp::Var("Bad function call"); |
111 } | 59 } |
112 | 60 |
113 return pp::Var(); | 61 return pp::Var(); |
114 } | 62 } |
115 | 63 |
116 } // namespace | |
117 | |
118 REGISTER_TEST_CASE(Instance); | 64 REGISTER_TEST_CASE(Instance); |
119 | 65 |
120 TestInstance::TestInstance(TestingInstance* instance) : TestCase(instance) { | 66 TestInstance::TestInstance(TestingInstance* instance) |
121 } | 67 : TestCase(instance), |
| 68 instance_so_(NULL) {} |
122 | 69 |
123 bool TestInstance::Init() { | 70 bool TestInstance::Init() { |
124 return true; | 71 return true; |
125 } | 72 } |
126 | 73 |
127 TestInstance::~TestInstance() { | 74 TestInstance::~TestInstance() { |
128 ResetTestObject(); | 75 ResetTestObject(); |
129 // When running tests in process, some post conditions check that teardown | 76 if (testing_interface_->IsOutOfProcess() == PP_FALSE) { |
130 // happened successfully. We need to run the garbage collector to ensure that | 77 // This should cause the instance object's descructor to be called. |
131 // vars get released. | |
132 if (testing_interface_->IsOutOfProcess() == PP_FALSE) | |
133 testing_interface_->RunV8GC(instance_->pp_instance()); | 78 testing_interface_->RunV8GC(instance_->pp_instance()); |
| 79 |
| 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(); |
| 101 } |
| 102 |
134 // Save the fact that we were destroyed in sessionStorage. This tests that | 103 // Save the fact that we were destroyed in sessionStorage. This tests that |
135 // we can ExecuteScript at instance destruction without crashing. It also | 104 // we can ExecuteScript at instance destruction without crashing. It also |
136 // allows us to check that ExecuteScript will run and succeed in certain | 105 // allows us to check that ExecuteScript will run and succeed in certain |
137 // cases. In particular, when the instance is destroyed by normal DOM | 106 // cases. In particular, when the instance is destroyed by normal DOM |
138 // deletion, ExecuteScript will actually work. See | 107 // deletion, ExecuteScript will actually work. See |
139 // TestExecuteScriptInInstanceShutdown for that test. Note, however, that | 108 // TestExecuteScriptInInstanceShutdown for that test. Note, however, that |
140 // ExecuteScript will *not* have an effect when the instance is destroyed | 109 // ExecuteScript will *not* have an effect when the instance is destroyed |
141 // because the renderer was shut down. | 110 // because the renderer was shut down. |
142 pp::Var ret = instance()->ExecuteScript( | 111 pp::Var ret = instance()->ExecuteScript( |
143 "sessionStorage.setItem('instance_destroyed', 'true');"); | 112 "sessionStorage.setItem('instance_destroyed', 'true');"); |
144 } | 113 } |
145 | 114 |
146 void TestInstance::RunTests(const std::string& filter) { | 115 void TestInstance::RunTests(const std::string& filter) { |
147 RUN_TEST(ExecuteScript, filter); | 116 RUN_TEST(ExecuteScript, filter); |
148 RUN_TEST(RecursiveObjects, filter); | 117 RUN_TEST(RecursiveObjects, filter); |
149 RUN_TEST(LeakedObjectDestructors, filter); | 118 RUN_TEST(LeakedObjectDestructors, filter); |
150 RUN_TEST(SetupExecuteScriptAtInstanceShutdown, filter); | 119 RUN_TEST(SetupExecuteScriptAtInstanceShutdown, filter); |
151 RUN_TEST(ExecuteScriptAtInstanceShutdown, filter); | 120 RUN_TEST(ExecuteScriptAtInstanceShutdown, filter); |
152 } | 121 } |
153 | 122 |
154 void TestInstance::LeakReferenceAndIgnore(const pp::Var& leaked) { | 123 void TestInstance::LeakReferenceAndIgnore(const pp::Var& leaked) { |
155 static const PPB_Var* var_interface = static_cast<const PPB_Var*>( | 124 static const PPB_Var* var_interface = static_cast<const PPB_Var*>( |
156 pp::Module::Get()->GetBrowserInterface(PPB_VAR_INTERFACE)); | 125 pp::Module::Get()->GetBrowserInterface(PPB_VAR_INTERFACE)); |
157 var_interface->AddRef(leaked.pp_var()); | 126 var_interface->AddRef(leaked.pp_var()); |
158 IgnoreLeakedVar(leaked.pp_var().value.as_id); | 127 IgnoreLeakedVar(leaked.pp_var().value.as_id); |
159 } | 128 } |
160 | 129 |
161 pp::deprecated::ScriptableObject* TestInstance::CreateTestObject() { | 130 pp::deprecated::ScriptableObject* TestInstance::CreateTestObject() { |
162 return new InstanceSO(this); | 131 if (!instance_so_) |
| 132 instance_so_ = new InstanceSO(this); |
| 133 return instance_so_; |
163 } | 134 } |
164 | 135 |
165 std::string TestInstance::TestExecuteScript() { | 136 std::string TestInstance::TestExecuteScript() { |
166 // Simple call back into the plugin. | 137 // Simple call back into the plugin. |
167 pp::Var exception; | 138 pp::Var exception; |
168 pp::Var ret = instance_->ExecuteScript( | 139 pp::Var ret = instance_->ExecuteScript( |
169 "document.getElementById('plugin').SetValue('hello, world');", | 140 "document.getElementById('plugin').SetValue('hello, world');", |
170 &exception); | 141 &exception); |
171 ASSERT_TRUE(ret.is_undefined()); | 142 ASSERT_TRUE(ret.is_undefined()); |
172 ASSERT_TRUE(exception.is_undefined()); | 143 ASSERT_TRUE(exception.is_undefined()); |
(...skipping 119 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
292 // that it was set as expected. | 263 // that it was set as expected. |
293 pp::Var result = instance()->ExecuteScript( | 264 pp::Var result = instance()->ExecuteScript( |
294 "sessionStorage.getItem('instance_destroyed');"); | 265 "sessionStorage.getItem('instance_destroyed');"); |
295 ASSERT_TRUE(result.is_string()); | 266 ASSERT_TRUE(result.is_string()); |
296 ASSERT_EQ(std::string("true"), result.AsString()); | 267 ASSERT_EQ(std::string("true"), result.AsString()); |
297 instance()->ExecuteScript("sessionStorage.removeItem('instance_destroyed');"); | 268 instance()->ExecuteScript("sessionStorage.removeItem('instance_destroyed');"); |
298 | 269 |
299 PASS(); | 270 PASS(); |
300 } | 271 } |
301 | 272 |
OLD | NEW |