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