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_Private* 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 287