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(¤t_as_object)) << i; 670 ASSERT_TRUE(current_as_object->Get(kKey, ¤t)) << 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