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