Home | History | Annotate | Download | only in pepper
      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, &current_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, &current_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