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