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 #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                             &current_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                         &current_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