• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 {
StackEntry__anon457c74610111::StackEntry36   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 {
HashedHandle__anon457c74610111::HashedHandle43   HashedHandle(v8::Handle<v8::Object> h) : handle(h) {}
hash__anon457c74610111::HashedHandle44   size_t hash() const { return handle->GetIdentityHash(); }
operator ==__anon457c74610111::HashedHandle45   bool operator==(const HashedHandle& h) const { return handle == h.handle; }
operator <__anon457c74610111::HashedHandle46   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> {
operator ()BASE_HASH_NAMESPACE::hash56   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.
GetOrCreateV8Value(v8::Isolate * isolate,const PP_Var & var,v8::Handle<v8::Value> * result,bool * did_create,VarHandleMap * visited_ids,ParentVarSet * parent_ids)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.
GetOrCreateVar(v8::Handle<v8::Value> val,v8::Handle<v8::Context> context,PP_Var * result,bool * did_create,HandleVarMap * visited_handles,ParentHandleSet * parent_handles,ResourceConverter * resource_converter)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 
CanHaveChildren(PP_Var var)246 bool CanHaveChildren(PP_Var var) {
247   return var.type == PP_VARTYPE_ARRAY || var.type == PP_VARTYPE_DICTIONARY;
248 }
249 
250 }  // namespace
251 
V8VarConverter(PP_Instance instance)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 
V8VarConverter(PP_Instance instance,scoped_ptr<ResourceConverter> resource_converter)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 
~V8VarConverter()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
ToV8Value(const PP_Var & var,v8::Handle<v8::Context> context,v8::Handle<v8::Value> * result)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 
FromV8Value(v8::Handle<v8::Value> val,v8::Handle<v8::Context> context,const base::Callback<void (const ScopedPPVar &,bool)> & callback)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