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