| 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 |