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 class V8ValueConverterImplTest : public testing::Test { 48 public: 49 V8ValueConverterImplTest() 50 : isolate_(v8::Isolate::GetCurrent()) { 51 } 52 53 protected: 54 virtual void SetUp() { 55 v8::HandleScope handle_scope(isolate_); 56 v8::Handle<v8::ObjectTemplate> global = v8::ObjectTemplate::New(isolate_); 57 context_.Reset(isolate_, v8::Context::New(isolate_, NULL, global)); 58 } 59 60 virtual void TearDown() { 61 context_.Reset(); 62 } 63 64 std::string GetString(base::DictionaryValue* value, const std::string& key) { 65 std::string temp; 66 if (!value->GetString(key, &temp)) { 67 ADD_FAILURE(); 68 return std::string(); 69 } 70 return temp; 71 } 72 73 std::string GetString(v8::Handle<v8::Object> value, const std::string& key) { 74 v8::Handle<v8::String> temp = 75 value->Get(v8::String::NewFromUtf8(isolate_, key.c_str())) 76 .As<v8::String>(); 77 if (temp.IsEmpty()) { 78 ADD_FAILURE(); 79 return std::string(); 80 } 81 v8::String::Utf8Value utf8(temp); 82 return std::string(*utf8, utf8.length()); 83 } 84 85 std::string GetString(base::ListValue* value, uint32 index) { 86 std::string temp; 87 if (!value->GetString(static_cast<size_t>(index), &temp)) { 88 ADD_FAILURE(); 89 return std::string(); 90 } 91 return temp; 92 } 93 94 std::string GetString(v8::Handle<v8::Array> value, uint32 index) { 95 v8::Handle<v8::String> temp = value->Get(index).As<v8::String>(); 96 if (temp.IsEmpty()) { 97 ADD_FAILURE(); 98 return std::string(); 99 } 100 v8::String::Utf8Value utf8(temp); 101 return std::string(*utf8, utf8.length()); 102 } 103 104 bool IsNull(base::DictionaryValue* value, const std::string& key) { 105 base::Value* child = NULL; 106 if (!value->Get(key, &child)) { 107 ADD_FAILURE(); 108 return false; 109 } 110 return child->GetType() == base::Value::TYPE_NULL; 111 } 112 113 bool IsNull(v8::Handle<v8::Object> value, const std::string& key) { 114 v8::Handle<v8::Value> child = 115 value->Get(v8::String::NewFromUtf8(isolate_, key.c_str())); 116 if (child.IsEmpty()) { 117 ADD_FAILURE(); 118 return false; 119 } 120 return child->IsNull(); 121 } 122 123 bool IsNull(base::ListValue* value, uint32 index) { 124 base::Value* child = NULL; 125 if (!value->Get(static_cast<size_t>(index), &child)) { 126 ADD_FAILURE(); 127 return false; 128 } 129 return child->GetType() == base::Value::TYPE_NULL; 130 } 131 132 bool IsNull(v8::Handle<v8::Array> value, uint32 index) { 133 v8::Handle<v8::Value> child = value->Get(index); 134 if (child.IsEmpty()) { 135 ADD_FAILURE(); 136 return false; 137 } 138 return child->IsNull(); 139 } 140 141 void TestWeirdType(const V8ValueConverterImpl& converter, 142 v8::Handle<v8::Value> val, 143 base::Value::Type expected_type, 144 scoped_ptr<base::Value> expected_value) { 145 v8::Local<v8::Context> context = 146 v8::Local<v8::Context>::New(isolate_, context_); 147 scoped_ptr<base::Value> raw(converter.FromV8Value(val, context)); 148 149 if (expected_value) { 150 ASSERT_TRUE(raw.get()); 151 EXPECT_TRUE(expected_value->Equals(raw.get())); 152 EXPECT_EQ(expected_type, raw->GetType()); 153 } else { 154 EXPECT_FALSE(raw.get()); 155 } 156 157 v8::Handle<v8::Object> object(v8::Object::New(isolate_)); 158 object->Set(v8::String::NewFromUtf8(isolate_, "test"), val); 159 scoped_ptr<base::DictionaryValue> dictionary( 160 static_cast<base::DictionaryValue*>( 161 converter.FromV8Value(object, context))); 162 ASSERT_TRUE(dictionary.get()); 163 164 if (expected_value) { 165 base::Value* temp = NULL; 166 ASSERT_TRUE(dictionary->Get("test", &temp)); 167 EXPECT_EQ(expected_type, temp->GetType()); 168 EXPECT_TRUE(expected_value->Equals(temp)); 169 } else { 170 EXPECT_FALSE(dictionary->HasKey("test")); 171 } 172 173 v8::Handle<v8::Array> array(v8::Array::New(isolate_)); 174 array->Set(0, val); 175 scoped_ptr<base::ListValue> list( 176 static_cast<base::ListValue*>(converter.FromV8Value(array, context))); 177 ASSERT_TRUE(list.get()); 178 if (expected_value) { 179 base::Value* temp = NULL; 180 ASSERT_TRUE(list->Get(0, &temp)); 181 EXPECT_EQ(expected_type, temp->GetType()); 182 EXPECT_TRUE(expected_value->Equals(temp)); 183 } else { 184 // Arrays should preserve their length, and convert unconvertible 185 // types into null. 186 base::Value* temp = NULL; 187 ASSERT_TRUE(list->Get(0, &temp)); 188 EXPECT_EQ(base::Value::TYPE_NULL, temp->GetType()); 189 } 190 } 191 192 v8::Isolate* isolate_; 193 194 // Context for the JavaScript in the test. 195 v8::Persistent<v8::Context> context_; 196 }; 197 198 TEST_F(V8ValueConverterImplTest, BasicRoundTrip) { 199 scoped_ptr<base::Value> original_root = base::test::ParseJson( 200 "{ \n" 201 " \"null\": null, \n" 202 " \"true\": true, \n" 203 " \"false\": false, \n" 204 " \"positive-int\": 42, \n" 205 " \"negative-int\": -42, \n" 206 " \"zero\": 0, \n" 207 " \"double\": 88.8, \n" 208 " \"big-integral-double\": 9007199254740992.0, \n" // 2.0^53 209 " \"string\": \"foobar\", \n" 210 " \"empty-string\": \"\", \n" 211 " \"dictionary\": { \n" 212 " \"foo\": \"bar\",\n" 213 " \"hot\": \"dog\",\n" 214 " }, \n" 215 " \"empty-dictionary\": {}, \n" 216 " \"list\": [ \"monkey\", \"balls\" ], \n" 217 " \"empty-list\": [], \n" 218 "}"); 219 220 v8::HandleScope handle_scope(isolate_); 221 v8::Local<v8::Context> context = 222 v8::Local<v8::Context>::New(isolate_, context_); 223 v8::Context::Scope context_scope(context); 224 225 V8ValueConverterImpl converter; 226 v8::Handle<v8::Object> v8_object = 227 converter.ToV8Value(original_root.get(), context).As<v8::Object>(); 228 ASSERT_FALSE(v8_object.IsEmpty()); 229 230 EXPECT_EQ(static_cast<const base::DictionaryValue&>(*original_root).size(), 231 v8_object->GetPropertyNames()->Length()); 232 EXPECT_TRUE( 233 v8_object->Get(v8::String::NewFromUtf8(isolate_, "null"))->IsNull()); 234 EXPECT_TRUE( 235 v8_object->Get(v8::String::NewFromUtf8(isolate_, "true"))->IsTrue()); 236 EXPECT_TRUE( 237 v8_object->Get(v8::String::NewFromUtf8(isolate_, "false"))->IsFalse()); 238 EXPECT_TRUE(v8_object->Get(v8::String::NewFromUtf8(isolate_, "positive-int")) 239 ->IsInt32()); 240 EXPECT_TRUE(v8_object->Get(v8::String::NewFromUtf8(isolate_, "negative-int")) 241 ->IsInt32()); 242 EXPECT_TRUE( 243 v8_object->Get(v8::String::NewFromUtf8(isolate_, "zero"))->IsInt32()); 244 EXPECT_TRUE( 245 v8_object->Get(v8::String::NewFromUtf8(isolate_, "double"))->IsNumber()); 246 EXPECT_TRUE(v8_object->Get(v8::String::NewFromUtf8( 247 isolate_, "big-integral-double"))->IsNumber()); 248 EXPECT_TRUE( 249 v8_object->Get(v8::String::NewFromUtf8(isolate_, "string"))->IsString()); 250 EXPECT_TRUE(v8_object->Get(v8::String::NewFromUtf8(isolate_, "empty-string")) 251 ->IsString()); 252 EXPECT_TRUE(v8_object->Get(v8::String::NewFromUtf8(isolate_, "dictionary")) 253 ->IsObject()); 254 EXPECT_TRUE(v8_object->Get(v8::String::NewFromUtf8( 255 isolate_, "empty-dictionary"))->IsObject()); 256 EXPECT_TRUE( 257 v8_object->Get(v8::String::NewFromUtf8(isolate_, "list"))->IsArray()); 258 EXPECT_TRUE(v8_object->Get(v8::String::NewFromUtf8(isolate_, "empty-list")) 259 ->IsArray()); 260 261 scoped_ptr<base::Value> new_root(converter.FromV8Value(v8_object, context)); 262 EXPECT_NE(original_root.get(), new_root.get()); 263 EXPECT_TRUE(original_root->Equals(new_root.get())); 264 } 265 266 TEST_F(V8ValueConverterImplTest, KeysWithDots) { 267 scoped_ptr<base::Value> original = 268 base::test::ParseJson("{ \"foo.bar\": \"baz\" }"); 269 270 v8::HandleScope handle_scope(isolate_); 271 v8::Local<v8::Context> context = 272 v8::Local<v8::Context>::New(isolate_, context_); 273 v8::Context::Scope context_scope(context); 274 275 V8ValueConverterImpl converter; 276 scoped_ptr<base::Value> copy( 277 converter.FromV8Value( 278 converter.ToV8Value(original.get(), context), context)); 279 280 EXPECT_TRUE(original->Equals(copy.get())); 281 } 282 283 TEST_F(V8ValueConverterImplTest, ObjectExceptions) { 284 v8::HandleScope handle_scope(isolate_); 285 v8::Local<v8::Context> context = 286 v8::Local<v8::Context>::New(isolate_, context_); 287 v8::Context::Scope context_scope(context); 288 289 // Set up objects to throw when reading or writing 'foo'. 290 const char* source = 291 "Object.prototype.__defineSetter__('foo', " 292 " function() { throw new Error('muah!'); });" 293 "Object.prototype.__defineGetter__('foo', " 294 " function() { throw new Error('muah!'); });"; 295 296 v8::Handle<v8::Script> script( 297 v8::Script::Compile(v8::String::NewFromUtf8(isolate_, source))); 298 script->Run(); 299 300 v8::Handle<v8::Object> object(v8::Object::New(isolate_)); 301 object->Set(v8::String::NewFromUtf8(isolate_, "bar"), 302 v8::String::NewFromUtf8(isolate_, "bar")); 303 304 // Converting from v8 value should replace the foo property with null. 305 V8ValueConverterImpl converter; 306 scoped_ptr<base::DictionaryValue> converted( 307 static_cast<base::DictionaryValue*>( 308 converter.FromV8Value(object, context))); 309 EXPECT_TRUE(converted.get()); 310 // http://code.google.com/p/v8/issues/detail?id=1342 311 // EXPECT_EQ(2u, converted->size()); 312 // EXPECT_TRUE(IsNull(converted.get(), "foo")); 313 EXPECT_EQ(1u, converted->size()); 314 EXPECT_EQ("bar", GetString(converted.get(), "bar")); 315 316 // Converting to v8 value should drop the foo property. 317 converted->SetString("foo", "foo"); 318 v8::Handle<v8::Object> copy = 319 converter.ToV8Value(converted.get(), context).As<v8::Object>(); 320 EXPECT_FALSE(copy.IsEmpty()); 321 EXPECT_EQ(2u, copy->GetPropertyNames()->Length()); 322 EXPECT_EQ("bar", GetString(copy, "bar")); 323 } 324 325 TEST_F(V8ValueConverterImplTest, ArrayExceptions) { 326 v8::HandleScope handle_scope(isolate_); 327 v8::Local<v8::Context> context = 328 v8::Local<v8::Context>::New(isolate_, context_); 329 v8::Context::Scope context_scope(context); 330 331 const char* source = "(function() {" 332 "var arr = [];" 333 "arr.__defineSetter__(0, " 334 " function() { throw new Error('muah!'); });" 335 "arr.__defineGetter__(0, " 336 " function() { throw new Error('muah!'); });" 337 "arr[1] = 'bar';" 338 "return arr;" 339 "})();"; 340 341 v8::Handle<v8::Script> script( 342 v8::Script::Compile(v8::String::NewFromUtf8(isolate_, source))); 343 v8::Handle<v8::Array> array = script->Run().As<v8::Array>(); 344 ASSERT_FALSE(array.IsEmpty()); 345 346 // Converting from v8 value should replace the first item with null. 347 V8ValueConverterImpl converter; 348 scoped_ptr<base::ListValue> converted(static_cast<base::ListValue*>( 349 converter.FromV8Value(array, context))); 350 ASSERT_TRUE(converted.get()); 351 // http://code.google.com/p/v8/issues/detail?id=1342 352 EXPECT_EQ(2u, converted->GetSize()); 353 EXPECT_TRUE(IsNull(converted.get(), 0)); 354 355 // Converting to v8 value should drop the first item and leave a hole. 356 converted.reset(static_cast<base::ListValue*>( 357 base::test::ParseJson("[ \"foo\", \"bar\" ]").release())); 358 v8::Handle<v8::Array> copy = 359 converter.ToV8Value(converted.get(), context).As<v8::Array>(); 360 ASSERT_FALSE(copy.IsEmpty()); 361 EXPECT_EQ(2u, copy->Length()); 362 EXPECT_EQ("bar", GetString(copy, 1)); 363 } 364 365 TEST_F(V8ValueConverterImplTest, WeirdTypes) { 366 v8::HandleScope handle_scope(isolate_); 367 v8::Local<v8::Context> context = 368 v8::Local<v8::Context>::New(isolate_, context_); 369 v8::Context::Scope context_scope(context); 370 371 v8::Handle<v8::RegExp> regex(v8::RegExp::New( 372 v8::String::NewFromUtf8(isolate_, "."), v8::RegExp::kNone)); 373 374 V8ValueConverterImpl converter; 375 TestWeirdType(converter, 376 v8::Undefined(isolate_), 377 base::Value::TYPE_NULL, // Arbitrary type, result is NULL. 378 scoped_ptr<base::Value>()); 379 TestWeirdType(converter, 380 v8::Date::New(isolate_, 1000), 381 base::Value::TYPE_DICTIONARY, 382 scoped_ptr<base::Value>(new base::DictionaryValue())); 383 TestWeirdType(converter, 384 regex, 385 base::Value::TYPE_DICTIONARY, 386 scoped_ptr<base::Value>(new base::DictionaryValue())); 387 388 converter.SetDateAllowed(true); 389 TestWeirdType(converter, 390 v8::Date::New(isolate_, 1000), 391 base::Value::TYPE_DOUBLE, 392 scoped_ptr<base::Value>(new base::FundamentalValue(1.0))); 393 394 converter.SetRegExpAllowed(true); 395 TestWeirdType(converter, 396 regex, 397 base::Value::TYPE_STRING, 398 scoped_ptr<base::Value>(new base::StringValue("/./"))); 399 } 400 401 TEST_F(V8ValueConverterImplTest, Prototype) { 402 v8::HandleScope handle_scope(isolate_); 403 v8::Local<v8::Context> context = 404 v8::Local<v8::Context>::New(isolate_, context_); 405 v8::Context::Scope context_scope(context); 406 407 const char* source = "(function() {" 408 "Object.prototype.foo = 'foo';" 409 "return {};" 410 "})();"; 411 412 v8::Handle<v8::Script> script( 413 v8::Script::Compile(v8::String::NewFromUtf8(isolate_, source))); 414 v8::Handle<v8::Object> object = script->Run().As<v8::Object>(); 415 ASSERT_FALSE(object.IsEmpty()); 416 417 V8ValueConverterImpl converter; 418 scoped_ptr<base::DictionaryValue> result( 419 static_cast<base::DictionaryValue*>( 420 converter.FromV8Value(object, context))); 421 ASSERT_TRUE(result.get()); 422 EXPECT_EQ(0u, result->size()); 423 } 424 425 TEST_F(V8ValueConverterImplTest, StripNullFromObjects) { 426 v8::HandleScope handle_scope(isolate_); 427 v8::Local<v8::Context> context = 428 v8::Local<v8::Context>::New(isolate_, context_); 429 v8::Context::Scope context_scope(context); 430 431 const char* source = "(function() {" 432 "return { foo: undefined, bar: null };" 433 "})();"; 434 435 v8::Handle<v8::Script> script( 436 v8::Script::Compile(v8::String::NewFromUtf8(isolate_, source))); 437 v8::Handle<v8::Object> object = script->Run().As<v8::Object>(); 438 ASSERT_FALSE(object.IsEmpty()); 439 440 V8ValueConverterImpl converter; 441 converter.SetStripNullFromObjects(true); 442 443 scoped_ptr<base::DictionaryValue> result( 444 static_cast<base::DictionaryValue*>( 445 converter.FromV8Value(object, context))); 446 ASSERT_TRUE(result.get()); 447 EXPECT_EQ(0u, result->size()); 448 } 449 450 TEST_F(V8ValueConverterImplTest, RecursiveObjects) { 451 v8::HandleScope handle_scope(isolate_); 452 v8::Local<v8::Context> context = 453 v8::Local<v8::Context>::New(isolate_, context_); 454 v8::Context::Scope context_scope(context); 455 456 V8ValueConverterImpl converter; 457 458 v8::Handle<v8::Object> object = v8::Object::New(isolate_).As<v8::Object>(); 459 ASSERT_FALSE(object.IsEmpty()); 460 object->Set(v8::String::NewFromUtf8(isolate_, "foo"), 461 v8::String::NewFromUtf8(isolate_, "bar")); 462 object->Set(v8::String::NewFromUtf8(isolate_, "obj"), object); 463 464 scoped_ptr<base::DictionaryValue> object_result( 465 static_cast<base::DictionaryValue*>( 466 converter.FromV8Value(object, context))); 467 ASSERT_TRUE(object_result.get()); 468 EXPECT_EQ(2u, object_result->size()); 469 EXPECT_TRUE(IsNull(object_result.get(), "obj")); 470 471 v8::Handle<v8::Array> array = v8::Array::New(isolate_).As<v8::Array>(); 472 ASSERT_FALSE(array.IsEmpty()); 473 array->Set(0, v8::String::NewFromUtf8(isolate_, "1")); 474 array->Set(1, array); 475 476 scoped_ptr<base::ListValue> list_result( 477 static_cast<base::ListValue*>(converter.FromV8Value(array, context))); 478 ASSERT_TRUE(list_result.get()); 479 EXPECT_EQ(2u, list_result->GetSize()); 480 EXPECT_TRUE(IsNull(list_result.get(), 1)); 481 } 482 483 TEST_F(V8ValueConverterImplTest, WeirdProperties) { 484 v8::HandleScope handle_scope(isolate_); 485 v8::Local<v8::Context> context = 486 v8::Local<v8::Context>::New(isolate_, context_); 487 v8::Context::Scope context_scope(context); 488 489 const char* source = "(function() {" 490 "return {" 491 "1: 'foo'," 492 "'2': 'bar'," 493 "true: 'baz'," 494 "false: 'qux'," 495 "null: 'quux'," 496 "undefined: 'oops'" 497 "};" 498 "})();"; 499 500 v8::Handle<v8::Script> script( 501 v8::Script::Compile(v8::String::NewFromUtf8(isolate_, source))); 502 v8::Handle<v8::Object> object = script->Run().As<v8::Object>(); 503 ASSERT_FALSE(object.IsEmpty()); 504 505 V8ValueConverterImpl converter; 506 scoped_ptr<base::Value> actual(converter.FromV8Value(object, context)); 507 508 scoped_ptr<base::Value> expected = base::test::ParseJson( 509 "{ \n" 510 " \"1\": \"foo\", \n" 511 " \"2\": \"bar\", \n" 512 " \"true\": \"baz\", \n" 513 " \"false\": \"qux\", \n" 514 " \"null\": \"quux\", \n" 515 " \"undefined\": \"oops\", \n" 516 "}"); 517 518 EXPECT_TRUE(expected->Equals(actual.get())); 519 } 520 521 TEST_F(V8ValueConverterImplTest, ArrayGetters) { 522 v8::HandleScope handle_scope(isolate_); 523 v8::Local<v8::Context> context = 524 v8::Local<v8::Context>::New(isolate_, context_); 525 v8::Context::Scope context_scope(context); 526 527 const char* source = "(function() {" 528 "var a = [0];" 529 "a.__defineGetter__(1, function() { return 'bar'; });" 530 "return a;" 531 "})();"; 532 533 v8::Handle<v8::Script> script( 534 v8::Script::Compile(v8::String::NewFromUtf8(isolate_, source))); 535 v8::Handle<v8::Array> array = script->Run().As<v8::Array>(); 536 ASSERT_FALSE(array.IsEmpty()); 537 538 V8ValueConverterImpl converter; 539 scoped_ptr<base::ListValue> result( 540 static_cast<base::ListValue*>(converter.FromV8Value(array, context))); 541 ASSERT_TRUE(result.get()); 542 EXPECT_EQ(2u, result->GetSize()); 543 } 544 545 TEST_F(V8ValueConverterImplTest, UndefinedValueBehavior) { 546 v8::HandleScope handle_scope(isolate_); 547 v8::Local<v8::Context> context = 548 v8::Local<v8::Context>::New(isolate_, context_); 549 v8::Context::Scope context_scope(context); 550 551 v8::Handle<v8::Object> object; 552 { 553 const char* source = "(function() {" 554 "return { foo: undefined, bar: null, baz: function(){} };" 555 "})();"; 556 v8::Handle<v8::Script> script( 557 v8::Script::Compile(v8::String::NewFromUtf8(isolate_, source))); 558 object = script->Run().As<v8::Object>(); 559 ASSERT_FALSE(object.IsEmpty()); 560 } 561 562 v8::Handle<v8::Array> array; 563 { 564 const char* source = "(function() {" 565 "return [ undefined, null, function(){} ];" 566 "})();"; 567 v8::Handle<v8::Script> script( 568 v8::Script::Compile(v8::String::NewFromUtf8(isolate_, source))); 569 array = script->Run().As<v8::Array>(); 570 ASSERT_FALSE(array.IsEmpty()); 571 } 572 573 v8::Handle<v8::Array> sparse_array; 574 { 575 const char* source = "(function() {" 576 "return new Array(3);" 577 "})();"; 578 v8::Handle<v8::Script> script( 579 v8::Script::Compile(v8::String::NewFromUtf8(isolate_, source))); 580 sparse_array = script->Run().As<v8::Array>(); 581 ASSERT_FALSE(sparse_array.IsEmpty()); 582 } 583 584 V8ValueConverterImpl converter; 585 586 scoped_ptr<base::Value> actual_object( 587 converter.FromV8Value(object, context)); 588 EXPECT_TRUE(base::Value::Equals( 589 base::test::ParseJson("{ \"bar\": null }").get(), actual_object.get())); 590 591 // Everything is null because JSON stringification preserves array length. 592 scoped_ptr<base::Value> actual_array(converter.FromV8Value(array, context)); 593 EXPECT_TRUE(base::Value::Equals( 594 base::test::ParseJson("[ null, null, null ]").get(), actual_array.get())); 595 596 scoped_ptr<base::Value> actual_sparse_array( 597 converter.FromV8Value(sparse_array, context)); 598 EXPECT_TRUE( 599 base::Value::Equals(base::test::ParseJson("[ null, null, null ]").get(), 600 actual_sparse_array.get())); 601 } 602 603 TEST_F(V8ValueConverterImplTest, ObjectsWithClashingIdentityHash) { 604 v8::HandleScope handle_scope(isolate_); 605 v8::Local<v8::Context> context = 606 v8::Local<v8::Context>::New(isolate_, context_); 607 v8::Context::Scope context_scope(context); 608 V8ValueConverterImpl converter; 609 610 // We check that the converter checks identity correctly by disabling the 611 // optimization of using identity hashes. 612 ScopedAvoidIdentityHashForTesting scoped_hash_avoider(&converter); 613 614 // Create the v8::Object to be converted. 615 v8::Handle<v8::Array> root(v8::Array::New(isolate_, 4)); 616 root->Set(0, v8::Handle<v8::Object>(v8::Object::New(isolate_))); 617 root->Set(1, v8::Handle<v8::Object>(v8::Object::New(isolate_))); 618 root->Set(2, v8::Handle<v8::Object>(v8::Array::New(isolate_, 0))); 619 root->Set(3, v8::Handle<v8::Object>(v8::Array::New(isolate_, 0))); 620 621 // The expected base::Value result. 622 scoped_ptr<base::Value> expected = base::test::ParseJson("[{},{},[],[]]"); 623 ASSERT_TRUE(expected.get()); 624 625 // The actual result. 626 scoped_ptr<base::Value> value(converter.FromV8Value(root, context)); 627 ASSERT_TRUE(value.get()); 628 629 EXPECT_TRUE(expected->Equals(value.get())); 630 } 631 632 TEST_F(V8ValueConverterImplTest, DetectCycles) { 633 v8::HandleScope handle_scope(isolate_); 634 v8::Local<v8::Context> context = 635 v8::Local<v8::Context>::New(isolate_, context_); 636 v8::Context::Scope context_scope(context); 637 V8ValueConverterImpl converter; 638 639 // Create a recursive array. 640 v8::Handle<v8::Array> recursive_array(v8::Array::New(isolate_, 1)); 641 recursive_array->Set(0, recursive_array); 642 643 // The first repetition should be trimmed and replaced by a null value. 644 base::ListValue expected_list; 645 expected_list.Append(base::Value::CreateNullValue()); 646 647 // The actual result. 648 scoped_ptr<base::Value> actual_list( 649 converter.FromV8Value(recursive_array, context)); 650 ASSERT_TRUE(actual_list.get()); 651 652 EXPECT_TRUE(expected_list.Equals(actual_list.get())); 653 654 // Now create a recursive object 655 const std::string key("key"); 656 v8::Handle<v8::Object> recursive_object(v8::Object::New(isolate_)); 657 v8::TryCatch try_catch; 658 recursive_object->Set( 659 v8::String::NewFromUtf8( 660 isolate_, key.c_str(), v8::String::kNormalString, key.length()), 661 recursive_object); 662 ASSERT_FALSE(try_catch.HasCaught()); 663 664 // The first repetition should be trimmed and replaced by a null value. 665 base::DictionaryValue expected_dictionary; 666 expected_dictionary.Set(key, base::Value::CreateNullValue()); 667 668 // The actual result. 669 scoped_ptr<base::Value> actual_dictionary( 670 converter.FromV8Value(recursive_object, context)); 671 ASSERT_TRUE(actual_dictionary.get()); 672 673 EXPECT_TRUE(expected_dictionary.Equals(actual_dictionary.get())); 674 } 675 676 TEST_F(V8ValueConverterImplTest, MaxRecursionDepth) { 677 v8::HandleScope handle_scope(isolate_); 678 v8::Local<v8::Context> context = 679 v8::Local<v8::Context>::New(isolate_, context_); 680 v8::Context::Scope context_scope(context); 681 682 // Must larger than kMaxRecursionDepth in v8_value_converter_impl.cc. 683 int kDepth = 1000; 684 const char kKey[] = "key"; 685 686 v8::Local<v8::Object> deep_object = v8::Object::New(isolate_); 687 688 v8::Local<v8::Object> leaf = deep_object; 689 for (int i = 0; i < kDepth; ++i) { 690 v8::Local<v8::Object> new_object = v8::Object::New(isolate_); 691 leaf->Set(v8::String::NewFromUtf8(isolate_, kKey), new_object); 692 leaf = new_object; 693 } 694 695 V8ValueConverterImpl converter; 696 scoped_ptr<base::Value> value(converter.FromV8Value(deep_object, context)); 697 ASSERT_TRUE(value); 698 699 // Expected depth is kMaxRecursionDepth in v8_value_converter_impl.cc. 700 int kExpectedDepth = 100; 701 702 base::Value* current = value.get(); 703 for (int i = 1; i < kExpectedDepth; ++i) { 704 base::DictionaryValue* current_as_object = NULL; 705 ASSERT_TRUE(current->GetAsDictionary(¤t_as_object)) << i; 706 ASSERT_TRUE(current_as_object->Get(kKey, ¤t)) << i; 707 } 708 709 // The leaf node shouldn't have any properties. 710 base::DictionaryValue empty; 711 EXPECT_TRUE(base::Value::Equals(&empty, current)) << *current; 712 } 713 714 class V8ValueConverterOverridingStrategyForTesting 715 : public V8ValueConverter::Strategy { 716 public: 717 V8ValueConverterOverridingStrategyForTesting() 718 : reference_value_(NewReferenceValue()) {} 719 virtual bool FromV8Object( 720 v8::Handle<v8::Object> value, 721 base::Value** out, 722 v8::Isolate* isolate, 723 const FromV8ValueCallback& callback) const OVERRIDE { 724 *out = NewReferenceValue(); 725 return true; 726 } 727 virtual bool FromV8Array(v8::Handle<v8::Array> value, 728 base::Value** out, 729 v8::Isolate* isolate, 730 const FromV8ValueCallback& callback) const OVERRIDE { 731 *out = NewReferenceValue(); 732 return true; 733 } 734 virtual bool FromV8ArrayBuffer(v8::Handle<v8::Object> value, 735 base::Value** out, 736 v8::Isolate* isolate) const OVERRIDE { 737 *out = NewReferenceValue(); 738 return true; 739 } 740 virtual bool FromV8Number(v8::Handle<v8::Number> value, 741 base::Value** out) const OVERRIDE { 742 *out = NewReferenceValue(); 743 return true; 744 } 745 virtual bool FromV8Undefined(base::Value** out) const OVERRIDE { 746 *out = NewReferenceValue(); 747 return true; 748 } 749 base::Value* reference_value() const { return reference_value_.get(); } 750 751 private: 752 static base::Value* NewReferenceValue() { 753 return new base::StringValue("strategy"); 754 } 755 scoped_ptr<base::Value> reference_value_; 756 }; 757 758 TEST_F(V8ValueConverterImplTest, StrategyOverrides) { 759 v8::HandleScope handle_scope(isolate_); 760 v8::Local<v8::Context> context = 761 v8::Local<v8::Context>::New(isolate_, context_); 762 v8::Context::Scope context_scope(context); 763 764 V8ValueConverterImpl converter; 765 V8ValueConverterOverridingStrategyForTesting strategy; 766 converter.SetStrategy(&strategy); 767 768 v8::Handle<v8::Object> object(v8::Object::New(isolate_)); 769 scoped_ptr<base::Value> object_value(converter.FromV8Value(object, context)); 770 ASSERT_TRUE(object_value); 771 EXPECT_TRUE( 772 base::Value::Equals(strategy.reference_value(), object_value.get())); 773 774 v8::Handle<v8::Array> array(v8::Array::New(isolate_)); 775 scoped_ptr<base::Value> array_value(converter.FromV8Value(array, context)); 776 ASSERT_TRUE(array_value); 777 EXPECT_TRUE( 778 base::Value::Equals(strategy.reference_value(), array_value.get())); 779 780 v8::Handle<v8::ArrayBuffer> array_buffer(v8::ArrayBuffer::New(isolate_, 0)); 781 scoped_ptr<base::Value> array_buffer_value( 782 converter.FromV8Value(array_buffer, context)); 783 ASSERT_TRUE(array_buffer_value); 784 EXPECT_TRUE(base::Value::Equals(strategy.reference_value(), 785 array_buffer_value.get())); 786 787 v8::Handle<v8::ArrayBufferView> array_buffer_view( 788 v8::Uint8Array::New(array_buffer, 0, 0)); 789 scoped_ptr<base::Value> array_buffer_view_value( 790 converter.FromV8Value(array_buffer_view, context)); 791 ASSERT_TRUE(array_buffer_view_value); 792 EXPECT_TRUE(base::Value::Equals(strategy.reference_value(), 793 array_buffer_view_value.get())); 794 795 v8::Handle<v8::Number> number(v8::Number::New(isolate_, 0.0)); 796 scoped_ptr<base::Value> number_value(converter.FromV8Value(number, context)); 797 ASSERT_TRUE(number_value); 798 EXPECT_TRUE( 799 base::Value::Equals(strategy.reference_value(), number_value.get())); 800 801 v8::Handle<v8::Primitive> undefined(v8::Undefined(isolate_)); 802 scoped_ptr<base::Value> undefined_value( 803 converter.FromV8Value(undefined, context)); 804 ASSERT_TRUE(undefined_value); 805 EXPECT_TRUE( 806 base::Value::Equals(strategy.reference_value(), undefined_value.get())); 807 } 808 809 class V8ValueConverterBypassStrategyForTesting 810 : public V8ValueConverter::Strategy { 811 public: 812 virtual bool FromV8Object( 813 v8::Handle<v8::Object> value, 814 base::Value** out, 815 v8::Isolate* isolate, 816 const FromV8ValueCallback& callback) const OVERRIDE { 817 return false; 818 } 819 virtual bool FromV8Array(v8::Handle<v8::Array> value, 820 base::Value** out, 821 v8::Isolate* isolate, 822 const FromV8ValueCallback& callback) const OVERRIDE { 823 return false; 824 } 825 virtual bool FromV8ArrayBuffer(v8::Handle<v8::Object> value, 826 base::Value** out, 827 v8::Isolate* isolate) const OVERRIDE { 828 return false; 829 } 830 virtual bool FromV8Number(v8::Handle<v8::Number> value, 831 base::Value** out) const OVERRIDE { 832 return false; 833 } 834 virtual bool FromV8Undefined(base::Value** out) const OVERRIDE { 835 return false; 836 } 837 }; 838 839 // Verify that having a strategy that fallbacks to default behaviour 840 // actually preserves it. 841 TEST_F(V8ValueConverterImplTest, StrategyBypass) { 842 v8::HandleScope handle_scope(isolate_); 843 v8::Local<v8::Context> context = 844 v8::Local<v8::Context>::New(isolate_, context_); 845 v8::Context::Scope context_scope(context); 846 847 V8ValueConverterImpl converter; 848 V8ValueConverterBypassStrategyForTesting strategy; 849 converter.SetStrategy(&strategy); 850 851 v8::Handle<v8::Object> object(v8::Object::New(isolate_)); 852 scoped_ptr<base::Value> object_value(converter.FromV8Value(object, context)); 853 ASSERT_TRUE(object_value); 854 scoped_ptr<base::Value> reference_object_value(base::test::ParseJson("{}")); 855 EXPECT_TRUE( 856 base::Value::Equals(reference_object_value.get(), object_value.get())); 857 858 v8::Handle<v8::Array> array(v8::Array::New(isolate_)); 859 scoped_ptr<base::Value> array_value(converter.FromV8Value(array, context)); 860 ASSERT_TRUE(array_value); 861 scoped_ptr<base::Value> reference_array_value(base::test::ParseJson("[]")); 862 EXPECT_TRUE( 863 base::Value::Equals(reference_array_value.get(), array_value.get())); 864 865 // Not testing ArrayBuffers as V8ValueConverter uses blink helpers and 866 // this requires having blink to be initialized. 867 868 v8::Handle<v8::Number> number(v8::Number::New(isolate_, 0.0)); 869 scoped_ptr<base::Value> number_value(converter.FromV8Value(number, context)); 870 ASSERT_TRUE(number_value); 871 scoped_ptr<base::Value> reference_number_value(base::test::ParseJson("0")); 872 EXPECT_TRUE( 873 base::Value::Equals(reference_number_value.get(), number_value.get())); 874 875 v8::Handle<v8::Primitive> undefined(v8::Undefined(isolate_)); 876 scoped_ptr<base::Value> undefined_value( 877 converter.FromV8Value(undefined, context)); 878 EXPECT_FALSE(undefined_value); 879 } 880 881 } // namespace content 882