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