Home | History | Annotate | Download | only in tests
      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 
    287