Home | History | Annotate | Download | only in pepper
      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 "content/renderer/pepper/v8_var_converter.h"
      6 
      7 #include <cmath>
      8 
      9 #include "base/logging.h"
     10 #include "base/memory/ref_counted.h"
     11 #include "base/memory/scoped_ptr.h"
     12 #include "base/values.h"
     13 #include "ppapi/c/pp_bool.h"
     14 #include "ppapi/c/pp_var.h"
     15 #include "ppapi/shared_impl/array_var.h"
     16 #include "ppapi/shared_impl/dictionary_var.h"
     17 #include "ppapi/shared_impl/ppapi_globals.h"
     18 #include "ppapi/shared_impl/proxy_lock.h"
     19 #include "ppapi/shared_impl/scoped_pp_var.h"
     20 #include "ppapi/shared_impl/test_globals.h"
     21 #include "ppapi/shared_impl/unittest_utils.h"
     22 #include "ppapi/shared_impl/var.h"
     23 #include "ppapi/shared_impl/var_tracker.h"
     24 #include "testing/gtest/include/gtest/gtest.h"
     25 #include "v8/include/v8.h"
     26 
     27 using ppapi::ArrayBufferVar;
     28 using ppapi::ArrayVar;
     29 using ppapi::DictionaryVar;
     30 using ppapi::PpapiGlobals;
     31 using ppapi::ProxyLock;
     32 using ppapi::ScopedPPVar;
     33 using ppapi::StringVar;
     34 using ppapi::TestGlobals;
     35 using ppapi::TestEqual;
     36 using ppapi::VarTracker;
     37 
     38 namespace content {
     39 
     40 namespace {
     41 
     42 // Maps PP_Var IDs to the V8 value handle they correspond to.
     43 typedef base::hash_map<int64_t, v8::Handle<v8::Value> > VarHandleMap;
     44 
     45 bool Equals(const PP_Var& var,
     46             v8::Handle<v8::Value> val,
     47             VarHandleMap* visited_ids) {
     48   if (ppapi::VarTracker::IsVarTypeRefcounted(var.type)) {
     49     VarHandleMap::iterator it = visited_ids->find(var.value.as_id);
     50     if (it != visited_ids->end())
     51       return it->second == val;
     52     (*visited_ids)[var.value.as_id] = val;
     53   }
     54 
     55   if (val->IsUndefined()) {
     56     return var.type == PP_VARTYPE_UNDEFINED;
     57   } else if (val->IsNull()) {
     58     return var.type == PP_VARTYPE_NULL;
     59   } else if (val->IsBoolean() || val->IsBooleanObject()) {
     60     return var.type == PP_VARTYPE_BOOL &&
     61         PP_FromBool(val->ToBoolean()->Value()) == var.value.as_bool;
     62   } else if (val->IsInt32()) {
     63     return var.type == PP_VARTYPE_INT32 &&
     64         val->ToInt32()->Value() == var.value.as_int;
     65   } else if (val->IsNumber() || val->IsNumberObject()) {
     66     return var.type == PP_VARTYPE_DOUBLE &&
     67         fabs(val->ToNumber()->Value() - var.value.as_double) <= 1.0e-4;
     68   } else if (val->IsString() || val->IsStringObject()) {
     69     if (var.type != PP_VARTYPE_STRING)
     70       return false;
     71     StringVar* string_var = StringVar::FromPPVar(var);
     72     DCHECK(string_var);
     73     v8::String::Utf8Value utf8(val->ToString());
     74     return std::string(*utf8, utf8.length()) == string_var->value();
     75   } else if (val->IsArray()) {
     76     if (var.type != PP_VARTYPE_ARRAY)
     77       return false;
     78     ArrayVar* array_var = ArrayVar::FromPPVar(var);
     79     DCHECK(array_var);
     80     v8::Handle<v8::Array> v8_array = val.As<v8::Array>();
     81     if (v8_array->Length() != array_var->elements().size())
     82       return false;
     83     for (uint32 i = 0; i < v8_array->Length(); ++i) {
     84       v8::Handle<v8::Value> child_v8 = v8_array->Get(i);
     85       if (!Equals(array_var->elements()[i].get(), child_v8, visited_ids))
     86         return false;
     87     }
     88     return true;
     89   } else if (val->IsObject()) {
     90     if (var.type == PP_VARTYPE_ARRAY_BUFFER) {
     91       // TODO(raymes): Implement this when we have tests for array buffers.
     92       NOTIMPLEMENTED();
     93       return false;
     94     } else {
     95       v8::Handle<v8::Object> v8_object = val->ToObject();
     96       if (var.type != PP_VARTYPE_DICTIONARY)
     97         return false;
     98       DictionaryVar* dict_var = DictionaryVar::FromPPVar(var);
     99       DCHECK(dict_var);
    100       v8::Handle<v8::Array> property_names(v8_object->GetOwnPropertyNames());
    101       if (property_names->Length() != dict_var->key_value_map().size())
    102         return false;
    103       for (uint32 i = 0; i < property_names->Length(); ++i) {
    104         v8::Handle<v8::Value> key(property_names->Get(i));
    105 
    106         if (!key->IsString() && !key->IsNumber())
    107           return false;
    108         v8::Handle<v8::Value> child_v8 = v8_object->Get(key);
    109 
    110         v8::String::Utf8Value name_utf8(key->ToString());
    111         ScopedPPVar release_key(ScopedPPVar::PassRef(),
    112             StringVar::StringToPPVar(
    113                 std::string(*name_utf8, name_utf8.length())));
    114         if (!dict_var->HasKey(release_key.get()))
    115           return false;
    116         ScopedPPVar release_value(ScopedPPVar::PassRef(),
    117                                   dict_var->Get(release_key.get()));
    118         if (!Equals(release_value.get(), child_v8, visited_ids))
    119           return false;
    120       }
    121       return true;
    122     }
    123   }
    124   return false;
    125 }
    126 
    127 bool Equals(const PP_Var& var,
    128             v8::Handle<v8::Value> val) {
    129   VarHandleMap var_handle_map;
    130   return Equals(var, val, &var_handle_map);
    131 }
    132 
    133 class V8VarConverterTest : public testing::Test {
    134  public:
    135   V8VarConverterTest()
    136       : isolate_(v8::Isolate::GetCurrent()) {}
    137   virtual ~V8VarConverterTest() {}
    138 
    139   // testing::Test implementation.
    140   virtual void SetUp() {
    141     ProxyLock::Acquire();
    142     v8::HandleScope handle_scope(isolate_);
    143     v8::Handle<v8::ObjectTemplate> global = v8::ObjectTemplate::New();
    144     context_.Reset(isolate_, v8::Context::New(isolate_, NULL, global));
    145   }
    146   virtual void TearDown() {
    147     context_.Dispose();
    148     ASSERT_TRUE(PpapiGlobals::Get()->GetVarTracker()->GetLiveVars().empty());
    149     ProxyLock::Release();
    150   }
    151 
    152  protected:
    153   bool RoundTrip(const PP_Var& var, PP_Var* result) {
    154     v8::HandleScope handle_scope(isolate_);
    155     v8::Context::Scope context_scope(isolate_, context_);
    156     v8::Local<v8::Context> context =
    157         v8::Local<v8::Context>::New(isolate_, context_);
    158     v8::Handle<v8::Value> v8_result;
    159     if (!V8VarConverter::ToV8Value(var, context, &v8_result))
    160       return false;
    161     if (!Equals(var, v8_result))
    162       return false;
    163     if (!V8VarConverter::FromV8Value(v8_result, context, result))
    164       return false;
    165     return true;
    166   }
    167 
    168   // Assumes a ref for var.
    169   bool RoundTripAndCompare(const PP_Var& var) {
    170     ScopedPPVar expected(ScopedPPVar::PassRef(), var);
    171     PP_Var actual_var;
    172     if (!RoundTrip(expected.get(), &actual_var))
    173       return false;
    174     ScopedPPVar actual(ScopedPPVar::PassRef(), actual_var);
    175     return TestEqual(expected.get(), actual.get());
    176   }
    177 
    178   v8::Isolate* isolate_;
    179 
    180   // Context for the JavaScript in the test.
    181   v8::Persistent<v8::Context> context_;
    182 
    183  private:
    184   TestGlobals globals_;
    185 };
    186 
    187 }  // namespace
    188 
    189 TEST_F(V8VarConverterTest, SimpleRoundTripTest) {
    190   EXPECT_TRUE(RoundTripAndCompare(PP_MakeUndefined()));
    191   EXPECT_TRUE(RoundTripAndCompare(PP_MakeNull()));
    192   EXPECT_TRUE(RoundTripAndCompare(PP_MakeInt32(100)));
    193   EXPECT_TRUE(RoundTripAndCompare(PP_MakeBool(PP_TRUE)));
    194   EXPECT_TRUE(RoundTripAndCompare(PP_MakeDouble(53.75)));
    195 }
    196 
    197 TEST_F(V8VarConverterTest, StringRoundTripTest) {
    198   EXPECT_TRUE(RoundTripAndCompare(StringVar::StringToPPVar("")));
    199   EXPECT_TRUE(RoundTripAndCompare(StringVar::StringToPPVar("hello world!")));
    200 }
    201 
    202 TEST_F(V8VarConverterTest, ArrayBufferRoundTripTest) {
    203   // TODO(raymes): Testing this here requires spinning up some of WebKit.
    204   // Work out how to do this.
    205 }
    206 
    207 TEST_F(V8VarConverterTest, DictionaryArrayRoundTripTest) {
    208   // Empty array.
    209   scoped_refptr<ArrayVar> array(new ArrayVar);
    210   ScopedPPVar release_array(ScopedPPVar::PassRef(), array->GetPPVar());
    211   EXPECT_TRUE(RoundTripAndCompare(array->GetPPVar()));
    212 
    213   size_t index = 0;
    214 
    215   // Array with primitives.
    216   array->Set(index++, PP_MakeUndefined());
    217   array->Set(index++, PP_MakeNull());
    218   array->Set(index++, PP_MakeInt32(100));
    219   array->Set(index++, PP_MakeBool(PP_FALSE));
    220   array->Set(index++, PP_MakeDouble(0.123));
    221   EXPECT_TRUE(RoundTripAndCompare(array->GetPPVar()));
    222 
    223   // Array with 2 references to the same string.
    224   ScopedPPVar release_string(
    225       ScopedPPVar::PassRef(), StringVar::StringToPPVar("abc"));
    226   array->Set(index++, release_string.get());
    227   array->Set(index++, release_string.get());
    228   EXPECT_TRUE(RoundTripAndCompare(array->GetPPVar()));
    229 
    230   // Array with nested array that references the same string.
    231   scoped_refptr<ArrayVar> array2(new ArrayVar);
    232   ScopedPPVar release_array2(ScopedPPVar::PassRef(), array2->GetPPVar());
    233   array2->Set(0, release_string.get());
    234   array->Set(index++, release_array2.get());
    235   EXPECT_TRUE(RoundTripAndCompare(array->GetPPVar()));
    236 
    237   // Empty dictionary.
    238   scoped_refptr<DictionaryVar> dictionary(new DictionaryVar);
    239   ScopedPPVar release_dictionary(ScopedPPVar::PassRef(),
    240                                  dictionary->GetPPVar());
    241   EXPECT_TRUE(RoundTripAndCompare(dictionary->GetPPVar()));
    242 
    243   // Dictionary with primitives.
    244   dictionary->SetWithStringKey("1", PP_MakeUndefined());
    245   dictionary->SetWithStringKey("2", PP_MakeNull());
    246   dictionary->SetWithStringKey("3", PP_MakeInt32(-100));
    247   dictionary->SetWithStringKey("4", PP_MakeBool(PP_TRUE));
    248   dictionary->SetWithStringKey("5", PP_MakeDouble(-103.52));
    249   EXPECT_TRUE(RoundTripAndCompare(dictionary->GetPPVar()));
    250 
    251   // Dictionary with 2 references to the same string.
    252   dictionary->SetWithStringKey("6", release_string.get());
    253   dictionary->SetWithStringKey("7", release_string.get());
    254   EXPECT_TRUE(RoundTripAndCompare(dictionary->GetPPVar()));
    255 
    256   // Dictionary with nested dictionary that references the same string.
    257   scoped_refptr<DictionaryVar> dictionary2(new DictionaryVar);
    258   ScopedPPVar release_dictionary2(ScopedPPVar::PassRef(),
    259                                   dictionary2->GetPPVar());
    260   dictionary2->SetWithStringKey("abc", release_string.get());
    261   dictionary->SetWithStringKey("8", release_dictionary2.get());
    262   EXPECT_TRUE(RoundTripAndCompare(dictionary->GetPPVar()));
    263 
    264   // Array with dictionary.
    265   array->Set(index++, release_dictionary.get());
    266   EXPECT_TRUE(RoundTripAndCompare(array->GetPPVar()));
    267 
    268   // Array with dictionary with array.
    269   array2->Set(0, PP_MakeInt32(100));
    270   dictionary->SetWithStringKey("9", release_array2.get());
    271   EXPECT_TRUE(RoundTripAndCompare(array->GetPPVar()));
    272 }
    273 
    274 TEST_F(V8VarConverterTest, Cycles) {
    275   // Check that cycles aren't converted.
    276   v8::HandleScope handle_scope(isolate_);
    277   v8::Context::Scope context_scope(isolate_, context_);
    278   v8::Local<v8::Context> context =
    279       v8::Local<v8::Context>::New(isolate_, context_);
    280 
    281   // Var->V8 conversion.
    282   {
    283     scoped_refptr<DictionaryVar> dictionary(new DictionaryVar);
    284     ScopedPPVar release_dictionary(ScopedPPVar::PassRef(),
    285                                    dictionary->GetPPVar());
    286     scoped_refptr<ArrayVar> array(new ArrayVar);
    287     ScopedPPVar release_array(ScopedPPVar::PassRef(), array->GetPPVar());
    288 
    289     dictionary->SetWithStringKey("1", release_array.get());
    290     array->Set(0, release_dictionary.get());
    291 
    292     v8::Handle<v8::Value> v8_result;
    293 
    294     // Array <-> dictionary cycle.
    295     dictionary->SetWithStringKey("1", release_array.get());
    296     ASSERT_FALSE(V8VarConverter::ToV8Value(release_dictionary.get(),
    297                                            context, &v8_result));
    298     // Break the cycle.
    299     // TODO(raymes): We need some better machinery for releasing vars with
    300     // cycles. Remove the code below once we have that.
    301     dictionary->DeleteWithStringKey("1");
    302 
    303     // Array with self reference.
    304     array->Set(0, release_array.get());
    305     ASSERT_FALSE(V8VarConverter::ToV8Value(release_array.get(),
    306                                            context, &v8_result));
    307     // Break the self reference.
    308     array->Set(0, PP_MakeUndefined());
    309   }
    310 
    311   // V8->Var conversion.
    312   {
    313     v8::Handle<v8::Object> object = v8::Object::New();
    314     v8::Handle<v8::Array> array = v8::Array::New();
    315 
    316     PP_Var var_result;
    317 
    318     // Array <-> dictionary cycle.
    319     std::string key = "1";
    320     object->Set(v8::String::New(key.c_str(), key.length()), array);
    321     array->Set(0, object);
    322 
    323     ASSERT_FALSE(V8VarConverter::FromV8Value(object, context, &var_result));
    324 
    325     // Array with self reference.
    326     array->Set(0, array);
    327     ASSERT_FALSE(V8VarConverter::FromV8Value(array, context, &var_result));
    328   }
    329 }
    330 
    331 TEST_F(V8VarConverterTest, StrangeDictionaryKeyTest) {
    332   {
    333     // Test keys with '.'.
    334     scoped_refptr<DictionaryVar> dictionary(new DictionaryVar);
    335     dictionary->SetWithStringKey(".", PP_MakeUndefined());
    336     dictionary->SetWithStringKey("x.y", PP_MakeUndefined());
    337     EXPECT_TRUE(RoundTripAndCompare(dictionary->GetPPVar()));
    338   }
    339 
    340   {
    341     // Test non-string key types. They should be cast to strings.
    342     v8::HandleScope handle_scope(isolate_);
    343     v8::Context::Scope context_scope(isolate_, context_);
    344 
    345     const char* source = "(function() {"
    346         "return {"
    347           "1: 'foo',"
    348           "'2': 'bar',"
    349           "true: 'baz',"
    350           "false: 'qux',"
    351           "null: 'quux',"
    352           "undefined: 'oops'"
    353         "};"
    354         "})();";
    355 
    356     v8::Handle<v8::Script> script(v8::Script::New(v8::String::New(source)));
    357     v8::Handle<v8::Object> object = script->Run().As<v8::Object>();
    358     ASSERT_FALSE(object.IsEmpty());
    359 
    360     PP_Var actual;
    361     ASSERT_TRUE(V8VarConverter::FromV8Value(object,
    362         v8::Local<v8::Context>::New(isolate_, context_), &actual));
    363     ScopedPPVar release_actual(ScopedPPVar::PassRef(), actual);
    364 
    365     scoped_refptr<DictionaryVar> expected(new DictionaryVar);
    366     ScopedPPVar foo(ScopedPPVar::PassRef(), StringVar::StringToPPVar("foo"));
    367     expected->SetWithStringKey("1", foo.get());
    368     ScopedPPVar bar(ScopedPPVar::PassRef(), StringVar::StringToPPVar("bar"));
    369     expected->SetWithStringKey("2", bar.get());
    370     ScopedPPVar baz(ScopedPPVar::PassRef(), StringVar::StringToPPVar("baz"));
    371     expected->SetWithStringKey("true", baz.get());
    372     ScopedPPVar qux(ScopedPPVar::PassRef(), StringVar::StringToPPVar("qux"));
    373     expected->SetWithStringKey("false", qux.get());
    374     ScopedPPVar quux(ScopedPPVar::PassRef(), StringVar::StringToPPVar("quux"));
    375     expected->SetWithStringKey("null", quux.get());
    376     ScopedPPVar oops(ScopedPPVar::PassRef(), StringVar::StringToPPVar("oops"));
    377     expected->SetWithStringKey("undefined", oops.get());
    378     ScopedPPVar release_expected(
    379         ScopedPPVar::PassRef(), expected->GetPPVar());
    380 
    381     ASSERT_TRUE(TestEqual(release_expected.get(), release_actual.get()));
    382   }
    383 }
    384 
    385 }  // namespace content
    386