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/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 
     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 content {
     64 namespace V8VarConverter {
     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(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.
    164 bool 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 
    228 bool 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
    244 bool 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, &current_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 
    347 bool 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, &current_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