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
15namespace 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.
22class 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
36ScopedAvoidIdentityHashForTesting::ScopedAvoidIdentityHashForTesting(
37    content::V8ValueConverterImpl* converter)
38    : converter_(converter) {
39  CHECK(converter_);
40  converter_->avoid_identity_hash_for_testing_ = true;
41}
42
43ScopedAvoidIdentityHashForTesting::~ScopedAvoidIdentityHashForTesting() {
44  converter_->avoid_identity_hash_for_testing_ = false;
45}
46
47namespace {
48
49// A dumb getter for an object's named callback.
50v8::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
57class 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
206TEST_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
261TEST_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
278TEST_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
318TEST_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
357TEST_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
391TEST_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
414TEST_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
438TEST_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
470TEST_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
507TEST_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
530TEST_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
569TEST_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
598TEST_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
640TEST_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