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: 48 virtual ~MockResourceConverter() {} 49 virtual void Flush(const base::Callback<void(bool)>& callback) OVERRIDE { 50 callback.Run(true); 51 } 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 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 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: 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 } 162 virtual ~V8VarConverterTest() {} 163 164 // testing::Test implementation. 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 } 171 virtual void TearDown() { 172 context_.Reset(); 173 ASSERT_TRUE(PpapiGlobals::Get()->GetVarTracker()->GetLiveVars().empty()); 174 ProxyLock::Release(); 175 } 176 177 protected: 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 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 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. 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 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 252 TEST_F(V8VarConverterTest, StringRoundTripTest) { 253 EXPECT_TRUE(RoundTripAndCompare(StringVar::StringToPPVar(""))); 254 EXPECT_TRUE(RoundTripAndCompare(StringVar::StringToPPVar("hello world!"))); 255 } 256 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 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 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 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