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