Home | History | Annotate | Download | only in renderer
      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 <cmath>
      6 
      7 #include "base/memory/scoped_ptr.h"
      8 #include "base/stl_util.h"
      9 #include "base/test/values_test_util.h"
     10 #include "base/values.h"
     11 #include "content/renderer/v8_value_converter_impl.h"
     12 #include "testing/gtest/include/gtest/gtest.h"
     13 #include "v8/include/v8.h"
     14 
     15 namespace content {
     16 
     17 // To improve the performance of
     18 // V8ValueConverterImpl::UpdateAndCheckUniqueness, identity hashes of objects
     19 // are used during checking for duplicates. For testing purposes we need to
     20 // ignore the hash sometimes. Create this helper object to avoid using identity
     21 // hashes for the lifetime of the helper.
     22 class ScopedAvoidIdentityHashForTesting {
     23  public:
     24   // The hashes will be ignored in |converter|, which must not be NULL and it
     25   // must outlive the created instance of this helper.
     26   explicit ScopedAvoidIdentityHashForTesting(
     27       content::V8ValueConverterImpl* converter);
     28   ~ScopedAvoidIdentityHashForTesting();
     29 
     30  private:
     31   content::V8ValueConverterImpl* converter_;
     32 
     33   DISALLOW_COPY_AND_ASSIGN(ScopedAvoidIdentityHashForTesting);
     34 };
     35 
     36 ScopedAvoidIdentityHashForTesting::ScopedAvoidIdentityHashForTesting(
     37     content::V8ValueConverterImpl* converter)
     38     : converter_(converter) {
     39   CHECK(converter_);
     40   converter_->avoid_identity_hash_for_testing_ = true;
     41 }
     42 
     43 ScopedAvoidIdentityHashForTesting::~ScopedAvoidIdentityHashForTesting() {
     44   converter_->avoid_identity_hash_for_testing_ = false;
     45 }
     46 
     47 namespace {
     48 
     49 // A dumb getter for an object's named callback.
     50 v8::Handle<v8::Value> NamedCallbackGetter(v8::Local<v8::String> name,
     51                                           const v8::AccessorInfo& info) {
     52   return v8::String::New("bar");
     53 }
     54 
     55 }  // namespace
     56 
     57 class V8ValueConverterImplTest : public testing::Test {
     58  public:
     59   V8ValueConverterImplTest()
     60       : isolate_(v8::Isolate::GetCurrent()) {
     61   }
     62 
     63  protected:
     64   virtual void SetUp() {
     65     v8::HandleScope handle_scope(isolate_);
     66     v8::Handle<v8::ObjectTemplate> global = v8::ObjectTemplate::New();
     67     context_.Reset(isolate_, v8::Context::New(isolate_, NULL, global));
     68   }
     69 
     70   virtual void TearDown() {
     71     context_.Dispose();
     72   }
     73 
     74   std::string GetString(base::DictionaryValue* value, const std::string& key) {
     75     std::string temp;
     76     if (!value->GetString(key, &temp)) {
     77       ADD_FAILURE();
     78       return std::string();
     79     }
     80     return temp;
     81   }
     82 
     83   std::string GetString(v8::Handle<v8::Object> value, const std::string& key) {
     84     v8::Handle<v8::String> temp =
     85         value->Get(v8::String::New(key.c_str())).As<v8::String>();
     86     if (temp.IsEmpty()) {
     87       ADD_FAILURE();
     88       return std::string();
     89     }
     90     v8::String::Utf8Value utf8(temp);
     91     return std::string(*utf8, utf8.length());
     92   }
     93 
     94   std::string GetString(base::ListValue* value, uint32 index) {
     95     std::string temp;
     96     if (!value->GetString(static_cast<size_t>(index), &temp)) {
     97       ADD_FAILURE();
     98       return std::string();
     99     }
    100     return temp;
    101   }
    102 
    103   std::string GetString(v8::Handle<v8::Array> value, uint32 index) {
    104     v8::Handle<v8::String> temp = value->Get(index).As<v8::String>();
    105     if (temp.IsEmpty()) {
    106       ADD_FAILURE();
    107       return std::string();
    108     }
    109     v8::String::Utf8Value utf8(temp);
    110     return std::string(*utf8, utf8.length());
    111   }
    112 
    113   bool IsNull(base::DictionaryValue* value, const std::string& key) {
    114     base::Value* child = NULL;
    115     if (!value->Get(key, &child)) {
    116       ADD_FAILURE();
    117       return false;
    118     }
    119     return child->GetType() == base::Value::TYPE_NULL;
    120   }
    121 
    122   bool IsNull(v8::Handle<v8::Object> value, const std::string& key) {
    123     v8::Handle<v8::Value> child = value->Get(v8::String::New(key.c_str()));
    124     if (child.IsEmpty()) {
    125       ADD_FAILURE();
    126       return false;
    127     }
    128     return child->IsNull();
    129   }
    130 
    131   bool IsNull(base::ListValue* value, uint32 index) {
    132     base::Value* child = NULL;
    133     if (!value->Get(static_cast<size_t>(index), &child)) {
    134       ADD_FAILURE();
    135       return false;
    136     }
    137     return child->GetType() == base::Value::TYPE_NULL;
    138   }
    139 
    140   bool IsNull(v8::Handle<v8::Array> value, uint32 index) {
    141     v8::Handle<v8::Value> child = value->Get(index);
    142     if (child.IsEmpty()) {
    143       ADD_FAILURE();
    144       return false;
    145     }
    146     return child->IsNull();
    147   }
    148 
    149   void TestWeirdType(const V8ValueConverterImpl& converter,
    150                      v8::Handle<v8::Value> val,
    151                      base::Value::Type expected_type,
    152                      scoped_ptr<base::Value> expected_value) {
    153     v8::Local<v8::Context> context =
    154         v8::Local<v8::Context>::New(isolate_, context_);
    155     scoped_ptr<base::Value> raw(converter.FromV8Value(val, context));
    156 
    157     if (expected_value) {
    158       ASSERT_TRUE(raw.get());
    159       EXPECT_TRUE(expected_value->Equals(raw.get()));
    160       EXPECT_EQ(expected_type, raw->GetType());
    161     } else {
    162       EXPECT_FALSE(raw.get());
    163     }
    164 
    165     v8::Handle<v8::Object> object(v8::Object::New());
    166     object->Set(v8::String::New("test"), val);
    167     scoped_ptr<base::DictionaryValue> dictionary(
    168         static_cast<base::DictionaryValue*>(
    169             converter.FromV8Value(object, context)));
    170     ASSERT_TRUE(dictionary.get());
    171 
    172     if (expected_value) {
    173       base::Value* temp = NULL;
    174       ASSERT_TRUE(dictionary->Get("test", &temp));
    175       EXPECT_EQ(expected_type, temp->GetType());
    176       EXPECT_TRUE(expected_value->Equals(temp));
    177     } else {
    178       EXPECT_FALSE(dictionary->HasKey("test"));
    179     }
    180 
    181     v8::Handle<v8::Array> array(v8::Array::New());
    182     array->Set(0, val);
    183     scoped_ptr<base::ListValue> list(
    184         static_cast<base::ListValue*>(converter.FromV8Value(array, context)));
    185     ASSERT_TRUE(list.get());
    186     if (expected_value) {
    187       base::Value* temp = NULL;
    188       ASSERT_TRUE(list->Get(0, &temp));
    189       EXPECT_EQ(expected_type, temp->GetType());
    190       EXPECT_TRUE(expected_value->Equals(temp));
    191     } else {
    192       // Arrays should preserve their length, and convert unconvertible
    193       // types into null.
    194       base::Value* temp = NULL;
    195       ASSERT_TRUE(list->Get(0, &temp));
    196       EXPECT_EQ(base::Value::TYPE_NULL, temp->GetType());
    197     }
    198   }
    199 
    200   v8::Isolate* isolate_;
    201 
    202   // Context for the JavaScript in the test.
    203   v8::Persistent<v8::Context> context_;
    204 };
    205 
    206 TEST_F(V8ValueConverterImplTest, BasicRoundTrip) {
    207   scoped_ptr<base::Value> original_root = base::test::ParseJson(
    208       "{ \n"
    209       "  \"null\": null, \n"
    210       "  \"true\": true, \n"
    211       "  \"false\": false, \n"
    212       "  \"positive-int\": 42, \n"
    213       "  \"negative-int\": -42, \n"
    214       "  \"zero\": 0, \n"
    215       "  \"double\": 88.8, \n"
    216       "  \"big-integral-double\": 9007199254740992.0, \n"  // 2.0^53
    217       "  \"string\": \"foobar\", \n"
    218       "  \"empty-string\": \"\", \n"
    219       "  \"dictionary\": { \n"
    220       "    \"foo\": \"bar\",\n"
    221       "    \"hot\": \"dog\",\n"
    222       "  }, \n"
    223       "  \"empty-dictionary\": {}, \n"
    224       "  \"list\": [ \"monkey\", \"balls\" ], \n"
    225       "  \"empty-list\": [], \n"
    226       "}");
    227 
    228   v8::HandleScope handle_scope(isolate_);
    229   v8::Context::Scope context_scope(isolate_, context_);
    230   v8::Local<v8::Context> context =
    231       v8::Local<v8::Context>::New(isolate_, context_);
    232 
    233   V8ValueConverterImpl converter;
    234   v8::Handle<v8::Object> v8_object =
    235       converter.ToV8Value(original_root.get(), context).As<v8::Object>();
    236   ASSERT_FALSE(v8_object.IsEmpty());
    237 
    238   EXPECT_EQ(static_cast<const base::DictionaryValue&>(*original_root).size(),
    239             v8_object->GetPropertyNames()->Length());
    240   EXPECT_TRUE(v8_object->Get(v8::String::New("null"))->IsNull());
    241   EXPECT_TRUE(v8_object->Get(v8::String::New("true"))->IsTrue());
    242   EXPECT_TRUE(v8_object->Get(v8::String::New("false"))->IsFalse());
    243   EXPECT_TRUE(v8_object->Get(v8::String::New("positive-int"))->IsInt32());
    244   EXPECT_TRUE(v8_object->Get(v8::String::New("negative-int"))->IsInt32());
    245   EXPECT_TRUE(v8_object->Get(v8::String::New("zero"))->IsInt32());
    246   EXPECT_TRUE(v8_object->Get(v8::String::New("double"))->IsNumber());
    247   EXPECT_TRUE(
    248       v8_object->Get(v8::String::New("big-integral-double"))->IsNumber());
    249   EXPECT_TRUE(v8_object->Get(v8::String::New("string"))->IsString());
    250   EXPECT_TRUE(v8_object->Get(v8::String::New("empty-string"))->IsString());
    251   EXPECT_TRUE(v8_object->Get(v8::String::New("dictionary"))->IsObject());
    252   EXPECT_TRUE(v8_object->Get(v8::String::New("empty-dictionary"))->IsObject());
    253   EXPECT_TRUE(v8_object->Get(v8::String::New("list"))->IsArray());
    254   EXPECT_TRUE(v8_object->Get(v8::String::New("empty-list"))->IsArray());
    255 
    256   scoped_ptr<base::Value> new_root(converter.FromV8Value(v8_object, context));
    257   EXPECT_NE(original_root.get(), new_root.get());
    258   EXPECT_TRUE(original_root->Equals(new_root.get()));
    259 }
    260 
    261 TEST_F(V8ValueConverterImplTest, KeysWithDots) {
    262   scoped_ptr<base::Value> original =
    263       base::test::ParseJson("{ \"foo.bar\": \"baz\" }");
    264 
    265   v8::HandleScope handle_scope(isolate_);
    266   v8::Context::Scope context_scope(isolate_, context_);
    267   v8::Local<v8::Context> context =
    268       v8::Local<v8::Context>::New(isolate_, context_);
    269 
    270   V8ValueConverterImpl converter;
    271   scoped_ptr<base::Value> copy(
    272       converter.FromV8Value(
    273           converter.ToV8Value(original.get(), context), context));
    274 
    275   EXPECT_TRUE(original->Equals(copy.get()));
    276 }
    277 
    278 TEST_F(V8ValueConverterImplTest, ObjectExceptions) {
    279   v8::HandleScope handle_scope(isolate_);
    280   v8::Context::Scope context_scope(isolate_, context_);
    281   v8::Local<v8::Context> context =
    282       v8::Local<v8::Context>::New(isolate_, context_);
    283 
    284   // Set up objects to throw when reading or writing 'foo'.
    285   const char* source =
    286       "Object.prototype.__defineSetter__('foo', "
    287       "    function() { throw new Error('muah!'); });"
    288       "Object.prototype.__defineGetter__('foo', "
    289       "    function() { throw new Error('muah!'); });";
    290 
    291   v8::Handle<v8::Script> script(v8::Script::New(v8::String::New(source)));
    292   script->Run();
    293 
    294   v8::Handle<v8::Object> object(v8::Object::New());
    295   object->Set(v8::String::New("bar"), v8::String::New("bar"));
    296 
    297   // Converting from v8 value should replace the foo property with null.
    298   V8ValueConverterImpl converter;
    299   scoped_ptr<base::DictionaryValue> converted(
    300       static_cast<base::DictionaryValue*>(
    301           converter.FromV8Value(object, context)));
    302   EXPECT_TRUE(converted.get());
    303   // http://code.google.com/p/v8/issues/detail?id=1342
    304   // EXPECT_EQ(2u, converted->size());
    305   // EXPECT_TRUE(IsNull(converted.get(), "foo"));
    306   EXPECT_EQ(1u, converted->size());
    307   EXPECT_EQ("bar", GetString(converted.get(), "bar"));
    308 
    309   // Converting to v8 value should drop the foo property.
    310   converted->SetString("foo", "foo");
    311   v8::Handle<v8::Object> copy =
    312       converter.ToV8Value(converted.get(), context).As<v8::Object>();
    313   EXPECT_FALSE(copy.IsEmpty());
    314   EXPECT_EQ(2u, copy->GetPropertyNames()->Length());
    315   EXPECT_EQ("bar", GetString(copy, "bar"));
    316 }
    317 
    318 TEST_F(V8ValueConverterImplTest, ArrayExceptions) {
    319   v8::HandleScope handle_scope(isolate_);
    320   v8::Context::Scope context_scope(isolate_, context_);
    321   v8::Local<v8::Context> context =
    322       v8::Local<v8::Context>::New(isolate_, context_);
    323 
    324   const char* source = "(function() {"
    325       "var arr = [];"
    326       "arr.__defineSetter__(0, "
    327       "    function() { throw new Error('muah!'); });"
    328       "arr.__defineGetter__(0, "
    329       "    function() { throw new Error('muah!'); });"
    330       "arr[1] = 'bar';"
    331       "return arr;"
    332       "})();";
    333 
    334   v8::Handle<v8::Script> script(v8::Script::New(v8::String::New(source)));
    335   v8::Handle<v8::Array> array = script->Run().As<v8::Array>();
    336   ASSERT_FALSE(array.IsEmpty());
    337 
    338   // Converting from v8 value should replace the first item with null.
    339   V8ValueConverterImpl converter;
    340   scoped_ptr<base::ListValue> converted(static_cast<base::ListValue*>(
    341       converter.FromV8Value(array, context)));
    342   ASSERT_TRUE(converted.get());
    343   // http://code.google.com/p/v8/issues/detail?id=1342
    344   EXPECT_EQ(2u, converted->GetSize());
    345   EXPECT_TRUE(IsNull(converted.get(), 0));
    346 
    347   // Converting to v8 value should drop the first item and leave a hole.
    348   converted.reset(static_cast<base::ListValue*>(
    349       base::test::ParseJson("[ \"foo\", \"bar\" ]").release()));
    350   v8::Handle<v8::Array> copy =
    351       converter.ToV8Value(converted.get(), context).As<v8::Array>();
    352   ASSERT_FALSE(copy.IsEmpty());
    353   EXPECT_EQ(2u, copy->Length());
    354   EXPECT_EQ("bar", GetString(copy, 1));
    355 }
    356 
    357 TEST_F(V8ValueConverterImplTest, WeirdTypes) {
    358   v8::HandleScope handle_scope(isolate_);
    359   v8::Context::Scope context_scope(isolate_, context_);
    360 
    361   v8::Handle<v8::RegExp> regex(
    362       v8::RegExp::New(v8::String::New("."), v8::RegExp::kNone));
    363 
    364   V8ValueConverterImpl converter;
    365   TestWeirdType(converter,
    366                 v8::Undefined(),
    367                 base::Value::TYPE_NULL,  // Arbitrary type, result is NULL.
    368                 scoped_ptr<base::Value>());
    369   TestWeirdType(converter,
    370                 v8::Date::New(1000),
    371                 base::Value::TYPE_DICTIONARY,
    372                 scoped_ptr<base::Value>(new base::DictionaryValue()));
    373   TestWeirdType(converter,
    374                 regex,
    375                 base::Value::TYPE_DICTIONARY,
    376                 scoped_ptr<base::Value>(new base::DictionaryValue()));
    377 
    378   converter.SetDateAllowed(true);
    379   TestWeirdType(converter,
    380                 v8::Date::New(1000),
    381                 base::Value::TYPE_DOUBLE,
    382                 scoped_ptr<base::Value>(new base::FundamentalValue(1.0)));
    383 
    384   converter.SetRegExpAllowed(true);
    385   TestWeirdType(converter,
    386                 regex,
    387                 base::Value::TYPE_STRING,
    388                 scoped_ptr<base::Value>(new base::StringValue("/./")));
    389 }
    390 
    391 TEST_F(V8ValueConverterImplTest, Prototype) {
    392   v8::HandleScope handle_scope(isolate_);
    393   v8::Context::Scope context_scope(isolate_, context_);
    394   v8::Local<v8::Context> context =
    395       v8::Local<v8::Context>::New(isolate_, context_);
    396 
    397   const char* source = "(function() {"
    398       "Object.prototype.foo = 'foo';"
    399       "return {};"
    400       "})();";
    401 
    402   v8::Handle<v8::Script> script(v8::Script::New(v8::String::New(source)));
    403   v8::Handle<v8::Object> object = script->Run().As<v8::Object>();
    404   ASSERT_FALSE(object.IsEmpty());
    405 
    406   V8ValueConverterImpl converter;
    407   scoped_ptr<base::DictionaryValue> result(
    408       static_cast<base::DictionaryValue*>(
    409           converter.FromV8Value(object, context)));
    410   ASSERT_TRUE(result.get());
    411   EXPECT_EQ(0u, result->size());
    412 }
    413 
    414 TEST_F(V8ValueConverterImplTest, StripNullFromObjects) {
    415   v8::HandleScope handle_scope(isolate_);
    416   v8::Context::Scope context_scope(isolate_, context_);
    417   v8::Local<v8::Context> context =
    418       v8::Local<v8::Context>::New(isolate_, context_);
    419 
    420   const char* source = "(function() {"
    421       "return { foo: undefined, bar: null };"
    422       "})();";
    423 
    424   v8::Handle<v8::Script> script(v8::Script::New(v8::String::New(source)));
    425   v8::Handle<v8::Object> object = script->Run().As<v8::Object>();
    426   ASSERT_FALSE(object.IsEmpty());
    427 
    428   V8ValueConverterImpl converter;
    429   converter.SetStripNullFromObjects(true);
    430 
    431   scoped_ptr<base::DictionaryValue> result(
    432       static_cast<base::DictionaryValue*>(
    433           converter.FromV8Value(object, context)));
    434   ASSERT_TRUE(result.get());
    435   EXPECT_EQ(0u, result->size());
    436 }
    437 
    438 TEST_F(V8ValueConverterImplTest, RecursiveObjects) {
    439   v8::HandleScope handle_scope(isolate_);
    440   v8::Context::Scope context_scope(isolate_, context_);
    441   v8::Local<v8::Context> context =
    442       v8::Local<v8::Context>::New(isolate_, context_);
    443 
    444   V8ValueConverterImpl converter;
    445 
    446   v8::Handle<v8::Object> object = v8::Object::New().As<v8::Object>();
    447   ASSERT_FALSE(object.IsEmpty());
    448   object->Set(v8::String::New("foo"), v8::String::New("bar"));
    449   object->Set(v8::String::New("obj"), object);
    450 
    451   scoped_ptr<base::DictionaryValue> object_result(
    452       static_cast<base::DictionaryValue*>(
    453           converter.FromV8Value(object, context)));
    454   ASSERT_TRUE(object_result.get());
    455   EXPECT_EQ(2u, object_result->size());
    456   EXPECT_TRUE(IsNull(object_result.get(), "obj"));
    457 
    458   v8::Handle<v8::Array> array = v8::Array::New().As<v8::Array>();
    459   ASSERT_FALSE(array.IsEmpty());
    460   array->Set(0, v8::String::New("1"));
    461   array->Set(1, array);
    462 
    463   scoped_ptr<base::ListValue> list_result(
    464       static_cast<base::ListValue*>(converter.FromV8Value(array, context)));
    465   ASSERT_TRUE(list_result.get());
    466   EXPECT_EQ(2u, list_result->GetSize());
    467   EXPECT_TRUE(IsNull(list_result.get(), 1));
    468 }
    469 
    470 TEST_F(V8ValueConverterImplTest, WeirdProperties) {
    471   v8::HandleScope handle_scope(isolate_);
    472   v8::Context::Scope context_scope(isolate_, context_);
    473   v8::Local<v8::Context> context =
    474       v8::Local<v8::Context>::New(isolate_, context_);
    475 
    476   const char* source = "(function() {"
    477       "return {"
    478         "1: 'foo',"
    479         "'2': 'bar',"
    480         "true: 'baz',"
    481         "false: 'qux',"
    482         "null: 'quux',"
    483         "undefined: 'oops'"
    484       "};"
    485       "})();";
    486 
    487   v8::Handle<v8::Script> script(v8::Script::New(v8::String::New(source)));
    488   v8::Handle<v8::Object> object = script->Run().As<v8::Object>();
    489   ASSERT_FALSE(object.IsEmpty());
    490 
    491   V8ValueConverterImpl converter;
    492   scoped_ptr<base::Value> actual(converter.FromV8Value(object, context));
    493 
    494   scoped_ptr<base::Value> expected = base::test::ParseJson(
    495       "{ \n"
    496       "  \"1\": \"foo\", \n"
    497       "  \"2\": \"bar\", \n"
    498       "  \"true\": \"baz\", \n"
    499       "  \"false\": \"qux\", \n"
    500       "  \"null\": \"quux\", \n"
    501       "  \"undefined\": \"oops\", \n"
    502       "}");
    503 
    504   EXPECT_TRUE(expected->Equals(actual.get()));
    505 }
    506 
    507 TEST_F(V8ValueConverterImplTest, ArrayGetters) {
    508   v8::HandleScope handle_scope(isolate_);
    509   v8::Context::Scope context_scope(isolate_, context_);
    510   v8::Local<v8::Context> context =
    511       v8::Local<v8::Context>::New(isolate_, context_);
    512 
    513   const char* source = "(function() {"
    514       "var a = [0];"
    515       "a.__defineGetter__(1, function() { return 'bar'; });"
    516       "return a;"
    517       "})();";
    518 
    519   v8::Handle<v8::Script> script(v8::Script::New(v8::String::New(source)));
    520   v8::Handle<v8::Array> array = script->Run().As<v8::Array>();
    521   ASSERT_FALSE(array.IsEmpty());
    522 
    523   V8ValueConverterImpl converter;
    524   scoped_ptr<base::ListValue> result(
    525       static_cast<base::ListValue*>(converter.FromV8Value(array, context)));
    526   ASSERT_TRUE(result.get());
    527   EXPECT_EQ(2u, result->GetSize());
    528 }
    529 
    530 TEST_F(V8ValueConverterImplTest, UndefinedValueBehavior) {
    531   v8::HandleScope handle_scope(isolate_);
    532   v8::Context::Scope context_scope(isolate_, context_);
    533   v8::Local<v8::Context> context =
    534       v8::Local<v8::Context>::New(isolate_, context_);
    535 
    536   v8::Handle<v8::Object> object;
    537   {
    538     const char* source = "(function() {"
    539         "return { foo: undefined, bar: null, baz: function(){} };"
    540         "})();";
    541     v8::Handle<v8::Script> script(v8::Script::New(v8::String::New(source)));
    542     object = script->Run().As<v8::Object>();
    543     ASSERT_FALSE(object.IsEmpty());
    544   }
    545 
    546   v8::Handle<v8::Array> array;
    547   {
    548     const char* source = "(function() {"
    549         "return [ undefined, null, function(){} ];"
    550         "})();";
    551     v8::Handle<v8::Script> script(v8::Script::New(v8::String::New(source)));
    552     array = script->Run().As<v8::Array>();
    553     ASSERT_FALSE(array.IsEmpty());
    554   }
    555 
    556   V8ValueConverterImpl converter;
    557 
    558   scoped_ptr<base::Value> actual_object(
    559       converter.FromV8Value(object, context));
    560   EXPECT_TRUE(base::Value::Equals(
    561       base::test::ParseJson("{ \"bar\": null }").get(), actual_object.get()));
    562 
    563   // Everything is null because JSON stringification preserves array length.
    564   scoped_ptr<Value> actual_array(converter.FromV8Value(array, context));
    565   EXPECT_TRUE(base::Value::Equals(
    566       base::test::ParseJson("[ null, null, null ]").get(), actual_array.get()));
    567 }
    568 
    569 TEST_F(V8ValueConverterImplTest, ObjectsWithClashingIdentityHash) {
    570   v8::HandleScope handle_scope(isolate_);
    571   v8::Context::Scope context_scope(isolate_, context_);
    572   v8::Local<v8::Context> context =
    573       v8::Local<v8::Context>::New(isolate_, context_);
    574   V8ValueConverterImpl converter;
    575 
    576   // We check that the converter checks identity correctly by disabling the
    577   // optimization of using identity hashes.
    578   ScopedAvoidIdentityHashForTesting scoped_hash_avoider(&converter);
    579 
    580   // Create the v8::Object to be converted.
    581   v8::Handle<v8::Array> root(v8::Array::New(4));
    582   root->Set(0, v8::Handle<v8::Object>(v8::Object::New()));
    583   root->Set(1, v8::Handle<v8::Object>(v8::Object::New()));
    584   root->Set(2, v8::Handle<v8::Object>(v8::Array::New(0)));
    585   root->Set(3, v8::Handle<v8::Object>(v8::Array::New(0)));
    586 
    587   // The expected base::Value result.
    588   scoped_ptr<base::Value> expected = base::test::ParseJson("[{},{},[],[]]");
    589   ASSERT_TRUE(expected.get());
    590 
    591   // The actual result.
    592   scoped_ptr<base::Value> value(converter.FromV8Value(root, context));
    593   ASSERT_TRUE(value.get());
    594 
    595   EXPECT_TRUE(expected->Equals(value.get()));
    596 }
    597 
    598 TEST_F(V8ValueConverterImplTest, DetectCycles) {
    599   v8::HandleScope handle_scope(isolate_);
    600   v8::Context::Scope context_scope(isolate_, context_);
    601   v8::Local<v8::Context> context =
    602       v8::Local<v8::Context>::New(isolate_, context_);
    603   V8ValueConverterImpl converter;
    604 
    605   // Create a recursive array.
    606   v8::Handle<v8::Array> recursive_array(v8::Array::New(1));
    607   recursive_array->Set(0, recursive_array);
    608 
    609   // The first repetition should be trimmed and replaced by a null value.
    610   base::ListValue expected_list;
    611   expected_list.Append(base::Value::CreateNullValue());
    612 
    613   // The actual result.
    614   scoped_ptr<base::Value> actual_list(
    615       converter.FromV8Value(recursive_array, context));
    616   ASSERT_TRUE(actual_list.get());
    617 
    618   EXPECT_TRUE(expected_list.Equals(actual_list.get()));
    619 
    620   // Now create a recursive object
    621   const std::string key("key");
    622   v8::Handle<v8::Object> recursive_object(v8::Object::New());
    623   v8::TryCatch try_catch;
    624   recursive_object->Set(v8::String::New(key.c_str(), key.length()),
    625                         recursive_object);
    626   ASSERT_FALSE(try_catch.HasCaught());
    627 
    628   // The first repetition should be trimmed and replaced by a null value.
    629   base::DictionaryValue expected_dictionary;
    630   expected_dictionary.Set(key, base::Value::CreateNullValue());
    631 
    632   // The actual result.
    633   scoped_ptr<base::Value> actual_dictionary(
    634       converter.FromV8Value(recursive_object, context));
    635   ASSERT_TRUE(actual_dictionary.get());
    636 
    637   EXPECT_TRUE(expected_dictionary.Equals(actual_dictionary.get()));
    638 }
    639 
    640 TEST_F(V8ValueConverterImplTest, MaxRecursionDepth) {
    641   v8::HandleScope handle_scope(isolate_);
    642   v8::Context::Scope context_scope(isolate_, context_);
    643   v8::Local<v8::Context> context =
    644       v8::Local<v8::Context>::New(isolate_, context_);
    645 
    646   // Must larger than kMaxRecursionDepth in v8_value_converter_impl.cc.
    647   int kDepth = 100;
    648   const char kKey[] = "key";
    649 
    650   v8::Local<v8::Object> deep_object = v8::Object::New();
    651 
    652   v8::Local<v8::Object> leaf = deep_object;
    653   for (int i = 0; i < kDepth; ++i) {
    654     v8::Local<v8::Object> new_object = v8::Object::New();
    655     leaf->Set(v8::String::New(kKey), new_object);
    656     leaf = new_object;
    657   }
    658 
    659   V8ValueConverterImpl converter;
    660   scoped_ptr<base::Value> value(converter.FromV8Value(deep_object, context));
    661   ASSERT_TRUE(value);
    662 
    663   // Expected depth is kMaxRecursionDepth in v8_value_converter_impl.cc.
    664   int kExpectedDepth = 10;
    665 
    666   base::Value* current = value.get();
    667   for (int i = 1; i < kExpectedDepth; ++i) {
    668     base::DictionaryValue* current_as_object = NULL;
    669     ASSERT_TRUE(current->GetAsDictionary(&current_as_object)) << i;
    670     ASSERT_TRUE(current_as_object->Get(kKey, &current)) << i;
    671   }
    672 
    673   // The leaf node shouldn't have any properties.
    674   base::DictionaryValue empty;
    675   EXPECT_TRUE(Value::Equals(&empty, current)) << *current;
    676 }
    677 
    678 }  // namespace content
    679