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