1 // Copyright (c) 2013 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 <map> 8 #include <stack> 9 #include <string> 10 11 #include "base/bind.h" 12 #include "base/containers/hash_tables.h" 13 #include "base/location.h" 14 #include "base/logging.h" 15 #include "base/memory/scoped_ptr.h" 16 #include "content/public/renderer/renderer_ppapi_host.h" 17 #include "content/renderer/pepper/host_array_buffer_var.h" 18 #include "content/renderer/pepper/host_globals.h" 19 #include "content/renderer/pepper/resource_converter.h" 20 #include "content/renderer/pepper/v8object_var.h" 21 #include "ppapi/shared_impl/array_var.h" 22 #include "ppapi/shared_impl/dictionary_var.h" 23 #include "ppapi/shared_impl/var.h" 24 #include "ppapi/shared_impl/var_tracker.h" 25 #include "third_party/WebKit/public/platform/WebArrayBuffer.h" 26 #include "third_party/WebKit/public/web/WebArrayBufferConverter.h" 27 28 using ppapi::ArrayBufferVar; 29 using ppapi::ArrayVar; 30 using ppapi::DictionaryVar; 31 using ppapi::ScopedPPVar; 32 using ppapi::StringVar; 33 using ppapi::V8ObjectVar; 34 using std::make_pair; 35 36 namespace { 37 38 template <class T> 39 struct StackEntry { 40 StackEntry(T v) : val(v), sentinel(false) {} 41 T val; 42 // Used to track parent nodes on the stack while traversing the graph. 43 bool sentinel; 44 }; 45 46 struct HashedHandle { 47 HashedHandle(v8::Handle<v8::Object> h) : handle(h) {} 48 size_t hash() const { return handle->GetIdentityHash(); } 49 bool operator==(const HashedHandle& h) const { return handle == h.handle; } 50 bool operator<(const HashedHandle& h) const { return hash() < h.hash(); } 51 v8::Handle<v8::Object> handle; 52 }; 53 54 } // namespace 55 56 namespace BASE_HASH_NAMESPACE { 57 #if defined(COMPILER_GCC) 58 template <> 59 struct hash<HashedHandle> { 60 size_t operator()(const HashedHandle& handle) const { return handle.hash(); } 61 }; 62 #elif defined(COMPILER_MSVC) 63 inline size_t hash_value(const HashedHandle& handle) { return handle.hash(); } 64 #endif 65 } // namespace BASE_HASH_NAMESPACE 66 67 namespace content { 68 69 namespace { 70 71 // Maps PP_Var IDs to the V8 value handle they correspond to. 72 typedef base::hash_map<int64_t, v8::Handle<v8::Value> > VarHandleMap; 73 typedef base::hash_set<int64_t> ParentVarSet; 74 75 // Maps V8 value handles to the PP_Var they correspond to. 76 typedef base::hash_map<HashedHandle, ScopedPPVar> HandleVarMap; 77 typedef base::hash_set<HashedHandle> ParentHandleSet; 78 79 // Returns a V8 value which corresponds to a given PP_Var. If |var| is a 80 // reference counted PP_Var type, and it exists in |visited_ids|, the V8 value 81 // associated with it in the map will be returned, otherwise a new V8 value will 82 // be created and added to the map. |did_create| indicates whether a new v8 83 // value was created as a result of calling the function. 84 bool GetOrCreateV8Value(v8::Handle<v8::Context> context, 85 const PP_Var& var, 86 V8VarConverter::AllowObjectVars object_vars_allowed, 87 v8::Handle<v8::Value>* result, 88 bool* did_create, 89 VarHandleMap* visited_ids, 90 ParentVarSet* parent_ids, 91 ResourceConverter* resource_converter) { 92 v8::Isolate* isolate = context->GetIsolate(); 93 *did_create = false; 94 95 if (ppapi::VarTracker::IsVarTypeRefcounted(var.type)) { 96 if (parent_ids->count(var.value.as_id) != 0) 97 return false; 98 VarHandleMap::iterator it = visited_ids->find(var.value.as_id); 99 if (it != visited_ids->end()) { 100 *result = it->second; 101 return true; 102 } 103 } 104 105 switch (var.type) { 106 case PP_VARTYPE_UNDEFINED: 107 *result = v8::Undefined(isolate); 108 break; 109 case PP_VARTYPE_NULL: 110 *result = v8::Null(isolate); 111 break; 112 case PP_VARTYPE_BOOL: 113 *result = (var.value.as_bool == PP_TRUE) ? v8::True(isolate) 114 : v8::False(isolate); 115 break; 116 case PP_VARTYPE_INT32: 117 *result = v8::Integer::New(isolate, var.value.as_int); 118 break; 119 case PP_VARTYPE_DOUBLE: 120 *result = v8::Number::New(isolate, var.value.as_double); 121 break; 122 case PP_VARTYPE_STRING: { 123 StringVar* string = StringVar::FromPPVar(var); 124 if (!string) { 125 NOTREACHED(); 126 result->Clear(); 127 return false; 128 } 129 const std::string& value = string->value(); 130 // Create a string primitive rather than a string object. This is lossy 131 // in the sense that string primitives in JavaScript can't be referenced 132 // in the same way that string vars can in pepper. But that information 133 // isn't very useful and primitive strings are a more expected form in JS. 134 *result = v8::String::NewFromUtf8( 135 isolate, value.c_str(), v8::String::kNormalString, value.size()); 136 break; 137 } 138 case PP_VARTYPE_ARRAY_BUFFER: { 139 ArrayBufferVar* buffer = ArrayBufferVar::FromPPVar(var); 140 if (!buffer) { 141 NOTREACHED(); 142 result->Clear(); 143 return false; 144 } 145 HostArrayBufferVar* host_buffer = 146 static_cast<HostArrayBufferVar*>(buffer); 147 *result = blink::WebArrayBufferConverter::toV8Value( 148 &host_buffer->webkit_buffer(), context->Global(), isolate); 149 break; 150 } 151 case PP_VARTYPE_ARRAY: 152 *result = v8::Array::New(isolate); 153 break; 154 case PP_VARTYPE_DICTIONARY: 155 *result = v8::Object::New(isolate); 156 break; 157 case PP_VARTYPE_OBJECT: { 158 // If object vars are disallowed, we should never be passed an object var 159 // to convert. Also, we should never expect to convert an object var which 160 // is nested inside an array or dictionary. 161 if (object_vars_allowed == V8VarConverter::kDisallowObjectVars || 162 visited_ids->size() != 0) { 163 NOTREACHED(); 164 result->Clear(); 165 return false; 166 } 167 scoped_refptr<V8ObjectVar> v8_object_var = V8ObjectVar::FromPPVar(var); 168 if (!v8_object_var.get()) { 169 NOTREACHED(); 170 result->Clear(); 171 return false; 172 } 173 *result = v8_object_var->GetHandle(); 174 break; 175 } 176 case PP_VARTYPE_RESOURCE: 177 if (!resource_converter->ToV8Value(var, context, result)) { 178 result->Clear(); 179 return false; 180 } 181 break; 182 } 183 184 *did_create = true; 185 if (ppapi::VarTracker::IsVarTypeRefcounted(var.type)) 186 (*visited_ids)[var.value.as_id] = *result; 187 return true; 188 } 189 190 // For a given V8 value handle, this returns a PP_Var which corresponds to it. 191 // If the handle already exists in |visited_handles|, the PP_Var associated with 192 // it will be returned, otherwise a new V8 value will be created and added to 193 // the map. |did_create| indicates if a new PP_Var was created as a result of 194 // calling the function. 195 bool GetOrCreateVar(v8::Handle<v8::Value> val, 196 v8::Handle<v8::Context> context, 197 PP_Instance instance, 198 V8VarConverter::AllowObjectVars object_vars_allowed, 199 PP_Var* result, 200 bool* did_create, 201 HandleVarMap* visited_handles, 202 ParentHandleSet* parent_handles, 203 ResourceConverter* resource_converter) { 204 CHECK(!val.IsEmpty()); 205 *did_create = false; 206 207 // Even though every v8 string primitive encountered will be a unique object, 208 // we still add them to |visited_handles| so that the corresponding string 209 // PP_Var created will be properly refcounted. 210 if (val->IsObject() || val->IsString()) { 211 if (parent_handles->count(HashedHandle(val->ToObject())) != 0) 212 return false; 213 214 HandleVarMap::const_iterator it = 215 visited_handles->find(HashedHandle(val->ToObject())); 216 if (it != visited_handles->end()) { 217 *result = it->second.get(); 218 return true; 219 } 220 } 221 222 v8::Isolate* isolate = context->GetIsolate(); 223 if (val->IsUndefined()) { 224 *result = PP_MakeUndefined(); 225 } else if (val->IsNull()) { 226 *result = PP_MakeNull(); 227 } else if (val->IsBoolean() || val->IsBooleanObject()) { 228 *result = PP_MakeBool(PP_FromBool(val->ToBoolean()->Value())); 229 } else if (val->IsInt32()) { 230 *result = PP_MakeInt32(val->ToInt32()->Value()); 231 } else if (val->IsNumber() || val->IsNumberObject()) { 232 *result = PP_MakeDouble(val->ToNumber()->Value()); 233 } else if (val->IsString() || val->IsStringObject()) { 234 v8::String::Utf8Value utf8(val->ToString()); 235 *result = StringVar::StringToPPVar(std::string(*utf8, utf8.length())); 236 } else if (val->IsObject()) { 237 // For any other v8 objects, the conversion happens as follows: 238 // 1) If the object is an array buffer, return an ArrayBufferVar. 239 // 2) If object vars are allowed, return the object wrapped as a 240 // V8ObjectVar. This is to maintain backward compatibility with 241 // synchronous scripting in Flash. 242 // 3) If the object is an array, return an ArrayVar. 243 // 4) If the object can be converted to a resource, return the ResourceVar. 244 // 5) Otherwise return a DictionaryVar. 245 scoped_ptr<blink::WebArrayBuffer> web_array_buffer( 246 blink::WebArrayBufferConverter::createFromV8Value(val, isolate)); 247 if (web_array_buffer.get()) { 248 scoped_refptr<HostArrayBufferVar> buffer_var( 249 new HostArrayBufferVar(*web_array_buffer)); 250 *result = buffer_var->GetPPVar(); 251 } else if (object_vars_allowed == V8VarConverter::kAllowObjectVars) { 252 v8::Handle<v8::Object> object = val->ToObject(); 253 *result = content::HostGlobals::Get()-> 254 host_var_tracker()->V8ObjectVarForV8Object(instance, object); 255 } else if (val->IsArray()) { 256 *result = (new ArrayVar())->GetPPVar(); 257 } else { 258 bool was_resource; 259 if (!resource_converter->FromV8Value( 260 val->ToObject(), context, result, &was_resource)) 261 return false; 262 if (!was_resource) { 263 *result = (new DictionaryVar())->GetPPVar(); 264 } 265 } 266 } else { 267 // Silently ignore the case where we can't convert to a Var as we may 268 // be trying to convert a type that doesn't have a corresponding 269 // PP_Var type. 270 return true; 271 } 272 273 *did_create = true; 274 if (val->IsObject() || val->IsString()) { 275 visited_handles->insert( 276 make_pair(HashedHandle(val->ToObject()), 277 ScopedPPVar(ScopedPPVar::PassRef(), *result))); 278 } 279 return true; 280 } 281 282 bool CanHaveChildren(PP_Var var) { 283 return var.type == PP_VARTYPE_ARRAY || var.type == PP_VARTYPE_DICTIONARY; 284 } 285 286 } // namespace 287 288 V8VarConverter::V8VarConverter(PP_Instance instance) 289 : instance_(instance), 290 object_vars_allowed_(kDisallowObjectVars), 291 message_loop_proxy_(base::MessageLoopProxy::current()) { 292 resource_converter_.reset(new ResourceConverterImpl( 293 instance, RendererPpapiHost::GetForPPInstance(instance))); 294 } 295 296 V8VarConverter::V8VarConverter(PP_Instance instance, 297 AllowObjectVars object_vars_allowed) 298 : instance_(instance), 299 object_vars_allowed_(object_vars_allowed), 300 message_loop_proxy_(base::MessageLoopProxy::current()) { 301 resource_converter_.reset(new ResourceConverterImpl( 302 instance, RendererPpapiHost::GetForPPInstance(instance))); 303 } 304 305 V8VarConverter::V8VarConverter(PP_Instance instance, 306 scoped_ptr<ResourceConverter> resource_converter) 307 : instance_(instance), 308 object_vars_allowed_(kDisallowObjectVars), 309 message_loop_proxy_(base::MessageLoopProxy::current()), 310 resource_converter_(resource_converter.release()) {} 311 312 V8VarConverter::~V8VarConverter() {} 313 314 // To/FromV8Value use a stack-based DFS search to traverse V8/Var graph. Each 315 // iteration, the top node on the stack examined. If the node has not been 316 // visited yet (i.e. sentinel == false) then it is added to the list of parents 317 // which contains all of the nodes on the path from the start node to the 318 // current node. Each of the current nodes children are examined. If they appear 319 // in the list of parents it means we have a cycle and we return NULL. 320 // Otherwise, if they can have children, we add them to the stack. If the 321 // node at the top of the stack has already been visited, then we pop it off the 322 // stack and erase it from the list of parents. 323 // static 324 bool V8VarConverter::ToV8Value(const PP_Var& var, 325 v8::Handle<v8::Context> context, 326 v8::Handle<v8::Value>* result) { 327 v8::Context::Scope context_scope(context); 328 v8::Isolate* isolate = context->GetIsolate(); 329 v8::EscapableHandleScope handle_scope(isolate); 330 331 VarHandleMap visited_ids; 332 ParentVarSet parent_ids; 333 334 std::stack<StackEntry<PP_Var> > stack; 335 stack.push(StackEntry<PP_Var>(var)); 336 v8::Local<v8::Value> root; 337 bool is_root = true; 338 339 while (!stack.empty()) { 340 const PP_Var& current_var = stack.top().val; 341 v8::Handle<v8::Value> current_v8; 342 343 if (stack.top().sentinel) { 344 stack.pop(); 345 if (CanHaveChildren(current_var)) 346 parent_ids.erase(current_var.value.as_id); 347 continue; 348 } else { 349 stack.top().sentinel = true; 350 } 351 352 bool did_create = false; 353 if (!GetOrCreateV8Value(context, 354 current_var, 355 object_vars_allowed_, 356 ¤t_v8, 357 &did_create, 358 &visited_ids, 359 &parent_ids, 360 resource_converter_.get())) { 361 return false; 362 } 363 364 if (is_root) { 365 is_root = false; 366 root = current_v8; 367 } 368 369 // Add child nodes to the stack. 370 if (current_var.type == PP_VARTYPE_ARRAY) { 371 parent_ids.insert(current_var.value.as_id); 372 ArrayVar* array_var = ArrayVar::FromPPVar(current_var); 373 if (!array_var) { 374 NOTREACHED(); 375 return false; 376 } 377 DCHECK(current_v8->IsArray()); 378 v8::Handle<v8::Array> v8_array = current_v8.As<v8::Array>(); 379 380 for (size_t i = 0; i < array_var->elements().size(); ++i) { 381 const PP_Var& child_var = array_var->elements()[i].get(); 382 v8::Handle<v8::Value> child_v8; 383 if (!GetOrCreateV8Value(context, 384 child_var, 385 object_vars_allowed_, 386 &child_v8, 387 &did_create, 388 &visited_ids, 389 &parent_ids, 390 resource_converter_.get())) { 391 return false; 392 } 393 if (did_create && CanHaveChildren(child_var)) 394 stack.push(child_var); 395 v8::TryCatch try_catch; 396 v8_array->Set(static_cast<uint32>(i), child_v8); 397 if (try_catch.HasCaught()) { 398 LOG(ERROR) << "Setter for index " << i << " threw an exception."; 399 return false; 400 } 401 } 402 } else if (current_var.type == PP_VARTYPE_DICTIONARY) { 403 parent_ids.insert(current_var.value.as_id); 404 DictionaryVar* dict_var = DictionaryVar::FromPPVar(current_var); 405 if (!dict_var) { 406 NOTREACHED(); 407 return false; 408 } 409 DCHECK(current_v8->IsObject()); 410 v8::Handle<v8::Object> v8_object = current_v8->ToObject(); 411 412 for (DictionaryVar::KeyValueMap::const_iterator iter = 413 dict_var->key_value_map().begin(); 414 iter != dict_var->key_value_map().end(); 415 ++iter) { 416 const std::string& key = iter->first; 417 const PP_Var& child_var = iter->second.get(); 418 v8::Handle<v8::Value> child_v8; 419 if (!GetOrCreateV8Value(context, 420 child_var, 421 object_vars_allowed_, 422 &child_v8, 423 &did_create, 424 &visited_ids, 425 &parent_ids, 426 resource_converter_.get())) { 427 return false; 428 } 429 if (did_create && CanHaveChildren(child_var)) 430 stack.push(child_var); 431 v8::TryCatch try_catch; 432 v8_object->Set( 433 v8::String::NewFromUtf8( 434 isolate, key.c_str(), v8::String::kNormalString, key.length()), 435 child_v8); 436 if (try_catch.HasCaught()) { 437 LOG(ERROR) << "Setter for property " << key.c_str() << " threw an " 438 << "exception."; 439 return false; 440 } 441 } 442 } 443 } 444 445 *result = handle_scope.Escape(root); 446 return true; 447 } 448 449 V8VarConverter::VarResult V8VarConverter::FromV8Value( 450 v8::Handle<v8::Value> val, 451 v8::Handle<v8::Context> context, 452 const base::Callback<void(const ScopedPPVar&, bool)>& callback) { 453 VarResult result; 454 result.success = FromV8ValueInternal(val, context, &result.var); 455 if (!result.success) 456 resource_converter_->Reset(); 457 result.completed_synchronously = !resource_converter_->NeedsFlush(); 458 if (!result.completed_synchronously) 459 resource_converter_->Flush(base::Bind(callback, result.var)); 460 461 return result; 462 } 463 464 bool V8VarConverter::FromV8ValueSync( 465 v8::Handle<v8::Value> val, 466 v8::Handle<v8::Context> context, 467 ppapi::ScopedPPVar* result_var) { 468 bool success = FromV8ValueInternal(val, context, result_var); 469 if (!success || resource_converter_->NeedsFlush()) { 470 resource_converter_->Reset(); 471 return false; 472 } 473 return true; 474 } 475 476 bool V8VarConverter::FromV8ValueInternal( 477 v8::Handle<v8::Value> val, 478 v8::Handle<v8::Context> context, 479 ppapi::ScopedPPVar* result_var) { 480 v8::Context::Scope context_scope(context); 481 v8::HandleScope handle_scope(context->GetIsolate()); 482 483 HandleVarMap visited_handles; 484 ParentHandleSet parent_handles; 485 486 std::stack<StackEntry<v8::Handle<v8::Value> > > stack; 487 stack.push(StackEntry<v8::Handle<v8::Value> >(val)); 488 ScopedPPVar root; 489 *result_var = PP_MakeUndefined(); 490 bool is_root = true; 491 492 while (!stack.empty()) { 493 v8::Handle<v8::Value> current_v8 = stack.top().val; 494 PP_Var current_var; 495 496 if (stack.top().sentinel) { 497 stack.pop(); 498 if (current_v8->IsObject()) 499 parent_handles.erase(HashedHandle(current_v8->ToObject())); 500 continue; 501 } else { 502 stack.top().sentinel = true; 503 } 504 505 bool did_create = false; 506 if (!GetOrCreateVar(current_v8, 507 context, 508 instance_, 509 object_vars_allowed_, 510 ¤t_var, 511 &did_create, 512 &visited_handles, 513 &parent_handles, 514 resource_converter_.get())) { 515 return false; 516 } 517 518 if (is_root) { 519 is_root = false; 520 root = current_var; 521 } 522 523 // Add child nodes to the stack. 524 if (current_var.type == PP_VARTYPE_ARRAY) { 525 DCHECK(current_v8->IsArray()); 526 v8::Handle<v8::Array> v8_array = current_v8.As<v8::Array>(); 527 parent_handles.insert(HashedHandle(v8_array)); 528 529 ArrayVar* array_var = ArrayVar::FromPPVar(current_var); 530 if (!array_var) { 531 NOTREACHED(); 532 return false; 533 } 534 535 for (uint32 i = 0; i < v8_array->Length(); ++i) { 536 v8::TryCatch try_catch; 537 v8::Handle<v8::Value> child_v8 = v8_array->Get(i); 538 if (try_catch.HasCaught()) 539 return false; 540 541 if (!v8_array->HasRealIndexedProperty(i)) 542 continue; 543 544 PP_Var child_var; 545 if (!GetOrCreateVar(child_v8, 546 context, 547 instance_, 548 object_vars_allowed_, 549 &child_var, 550 &did_create, 551 &visited_handles, 552 &parent_handles, 553 resource_converter_.get())) { 554 return false; 555 } 556 if (did_create && child_v8->IsObject()) 557 stack.push(child_v8); 558 559 array_var->Set(i, child_var); 560 } 561 } else if (current_var.type == PP_VARTYPE_DICTIONARY) { 562 DCHECK(current_v8->IsObject()); 563 v8::Handle<v8::Object> v8_object = current_v8->ToObject(); 564 parent_handles.insert(HashedHandle(v8_object)); 565 566 DictionaryVar* dict_var = DictionaryVar::FromPPVar(current_var); 567 if (!dict_var) { 568 NOTREACHED(); 569 return false; 570 } 571 572 v8::Handle<v8::Array> property_names(v8_object->GetOwnPropertyNames()); 573 for (uint32 i = 0; i < property_names->Length(); ++i) { 574 v8::Handle<v8::Value> key(property_names->Get(i)); 575 576 // Extend this test to cover more types as necessary and if sensible. 577 if (!key->IsString() && !key->IsNumber()) { 578 NOTREACHED() << "Key \"" << *v8::String::Utf8Value(key) 579 << "\" " 580 "is neither a string nor a number"; 581 return false; 582 } 583 584 // Skip all callbacks: crbug.com/139933 585 if (v8_object->HasRealNamedCallbackProperty(key->ToString())) 586 continue; 587 588 v8::String::Utf8Value name_utf8(key->ToString()); 589 590 v8::TryCatch try_catch; 591 v8::Handle<v8::Value> child_v8 = v8_object->Get(key); 592 if (try_catch.HasCaught()) 593 return false; 594 595 PP_Var child_var; 596 if (!GetOrCreateVar(child_v8, 597 context, 598 instance_, 599 object_vars_allowed_, 600 &child_var, 601 &did_create, 602 &visited_handles, 603 &parent_handles, 604 resource_converter_.get())) { 605 return false; 606 } 607 if (did_create && child_v8->IsObject()) 608 stack.push(child_v8); 609 610 bool success = dict_var->SetWithStringKey( 611 std::string(*name_utf8, name_utf8.length()), child_var); 612 DCHECK(success); 613 } 614 } 615 } 616 *result_var = root; 617 return true; 618 } 619 620 } // namespace content 621