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