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/host_globals.h"
19#include "content/renderer/pepper/resource_converter.h"
20#include "content/renderer/pepper/v8object_var.h"
21#include "ppapi/shared_impl/array_var.h"
22#include "ppapi/shared_impl/dictionary_var.h"
23#include "ppapi/shared_impl/var.h"
24#include "ppapi/shared_impl/var_tracker.h"
25#include "third_party/WebKit/public/platform/WebArrayBuffer.h"
26#include "third_party/WebKit/public/web/WebArrayBufferConverter.h"
27
28using ppapi::ArrayBufferVar;
29using ppapi::ArrayVar;
30using ppapi::DictionaryVar;
31using ppapi::ScopedPPVar;
32using ppapi::StringVar;
33using ppapi::V8ObjectVar;
34using std::make_pair;
35
36namespace {
37
38template <class T>
39struct StackEntry {
40  StackEntry(T v) : val(v), sentinel(false) {}
41  T val;
42  // Used to track parent nodes on the stack while traversing the graph.
43  bool sentinel;
44};
45
46struct HashedHandle {
47  HashedHandle(v8::Handle<v8::Object> h) : handle(h) {}
48  size_t hash() const { return handle->GetIdentityHash(); }
49  bool operator==(const HashedHandle& h) const { return handle == h.handle; }
50  bool operator<(const HashedHandle& h) const { return hash() < h.hash(); }
51  v8::Handle<v8::Object> handle;
52};
53
54}  // namespace
55
56namespace BASE_HASH_NAMESPACE {
57#if defined(COMPILER_GCC)
58template <>
59struct hash<HashedHandle> {
60  size_t operator()(const HashedHandle& handle) const { return handle.hash(); }
61};
62#elif defined(COMPILER_MSVC)
63inline size_t hash_value(const HashedHandle& handle) { return handle.hash(); }
64#endif
65}  // namespace BASE_HASH_NAMESPACE
66
67namespace content {
68
69namespace {
70
71// Maps PP_Var IDs to the V8 value handle they correspond to.
72typedef base::hash_map<int64_t, v8::Handle<v8::Value> > VarHandleMap;
73typedef base::hash_set<int64_t> ParentVarSet;
74
75// Maps V8 value handles to the PP_Var they correspond to.
76typedef base::hash_map<HashedHandle, ScopedPPVar> HandleVarMap;
77typedef 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.
84bool GetOrCreateV8Value(v8::Handle<v8::Context> context,
85                        const PP_Var& var,
86                        V8VarConverter::AllowObjectVars object_vars_allowed,
87                        v8::Handle<v8::Value>* result,
88                        bool* did_create,
89                        VarHandleMap* visited_ids,
90                        ParentVarSet* parent_ids,
91                        ResourceConverter* resource_converter) {
92  v8::Isolate* isolate = context->GetIsolate();
93  *did_create = false;
94
95  if (ppapi::VarTracker::IsVarTypeRefcounted(var.type)) {
96    if (parent_ids->count(var.value.as_id) != 0)
97      return false;
98    VarHandleMap::iterator it = visited_ids->find(var.value.as_id);
99    if (it != visited_ids->end()) {
100      *result = it->second;
101      return true;
102    }
103  }
104
105  switch (var.type) {
106    case PP_VARTYPE_UNDEFINED:
107      *result = v8::Undefined(isolate);
108      break;
109    case PP_VARTYPE_NULL:
110      *result = v8::Null(isolate);
111      break;
112    case PP_VARTYPE_BOOL:
113      *result = (var.value.as_bool == PP_TRUE) ? v8::True(isolate)
114                                               : v8::False(isolate);
115      break;
116    case PP_VARTYPE_INT32:
117      *result = v8::Integer::New(isolate, var.value.as_int);
118      break;
119    case PP_VARTYPE_DOUBLE:
120      *result = v8::Number::New(isolate, var.value.as_double);
121      break;
122    case PP_VARTYPE_STRING: {
123      StringVar* string = StringVar::FromPPVar(var);
124      if (!string) {
125        NOTREACHED();
126        result->Clear();
127        return false;
128      }
129      const std::string& value = string->value();
130      // Create a string primitive rather than a string object. This is lossy
131      // in the sense that string primitives in JavaScript can't be referenced
132      // in the same way that string vars can in pepper. But that information
133      // isn't very useful and primitive strings are a more expected form in JS.
134      *result = v8::String::NewFromUtf8(
135          isolate, value.c_str(), v8::String::kNormalString, value.size());
136      break;
137    }
138    case PP_VARTYPE_ARRAY_BUFFER: {
139      ArrayBufferVar* buffer = ArrayBufferVar::FromPPVar(var);
140      if (!buffer) {
141        NOTREACHED();
142        result->Clear();
143        return false;
144      }
145      HostArrayBufferVar* host_buffer =
146          static_cast<HostArrayBufferVar*>(buffer);
147      *result = blink::WebArrayBufferConverter::toV8Value(
148          &host_buffer->webkit_buffer(), context->Global(), isolate);
149      break;
150    }
151    case PP_VARTYPE_ARRAY:
152      *result = v8::Array::New(isolate);
153      break;
154    case PP_VARTYPE_DICTIONARY:
155      *result = v8::Object::New(isolate);
156      break;
157    case PP_VARTYPE_OBJECT: {
158      // If object vars are disallowed, we should never be passed an object var
159      // to convert. Also, we should never expect to convert an object var which
160      // is nested inside an array or dictionary.
161      if (object_vars_allowed == V8VarConverter::kDisallowObjectVars ||
162          visited_ids->size() != 0) {
163        NOTREACHED();
164        result->Clear();
165        return false;
166      }
167      scoped_refptr<V8ObjectVar> v8_object_var = V8ObjectVar::FromPPVar(var);
168      if (!v8_object_var.get()) {
169        NOTREACHED();
170        result->Clear();
171        return false;
172      }
173      *result = v8_object_var->GetHandle();
174      break;
175    }
176    case PP_VARTYPE_RESOURCE:
177      if (!resource_converter->ToV8Value(var, context, result)) {
178        result->Clear();
179        return false;
180      }
181      break;
182  }
183
184  *did_create = true;
185  if (ppapi::VarTracker::IsVarTypeRefcounted(var.type))
186    (*visited_ids)[var.value.as_id] = *result;
187  return true;
188}
189
190// For a given V8 value handle, this returns a PP_Var which corresponds to it.
191// If the handle already exists in |visited_handles|, the PP_Var associated with
192// it will be returned, otherwise a new V8 value will be created and added to
193// the map. |did_create| indicates if a new PP_Var was created as a result of
194// calling the function.
195bool GetOrCreateVar(v8::Handle<v8::Value> val,
196                    v8::Handle<v8::Context> context,
197                    PP_Instance instance,
198                    V8VarConverter::AllowObjectVars object_vars_allowed,
199                    PP_Var* result,
200                    bool* did_create,
201                    HandleVarMap* visited_handles,
202                    ParentHandleSet* parent_handles,
203                    ResourceConverter* resource_converter) {
204  CHECK(!val.IsEmpty());
205  *did_create = false;
206
207  // Even though every v8 string primitive encountered will be a unique object,
208  // we still add them to |visited_handles| so that the corresponding string
209  // PP_Var created will be properly refcounted.
210  if (val->IsObject() || val->IsString()) {
211    if (parent_handles->count(HashedHandle(val->ToObject())) != 0)
212      return false;
213
214    HandleVarMap::const_iterator it =
215        visited_handles->find(HashedHandle(val->ToObject()));
216    if (it != visited_handles->end()) {
217      *result = it->second.get();
218      return true;
219    }
220  }
221
222  v8::Isolate* isolate = context->GetIsolate();
223  if (val->IsUndefined()) {
224    *result = PP_MakeUndefined();
225  } else if (val->IsNull()) {
226    *result = PP_MakeNull();
227  } else if (val->IsBoolean() || val->IsBooleanObject()) {
228    *result = PP_MakeBool(PP_FromBool(val->ToBoolean()->Value()));
229  } else if (val->IsInt32()) {
230    *result = PP_MakeInt32(val->ToInt32()->Value());
231  } else if (val->IsNumber() || val->IsNumberObject()) {
232    *result = PP_MakeDouble(val->ToNumber()->Value());
233  } else if (val->IsString() || val->IsStringObject()) {
234    v8::String::Utf8Value utf8(val->ToString());
235    *result = StringVar::StringToPPVar(std::string(*utf8, utf8.length()));
236  } else if (val->IsObject()) {
237    // For any other v8 objects, the conversion happens as follows:
238    // 1) If the object is an array buffer, return an ArrayBufferVar.
239    // 2) If object vars are allowed, return the object wrapped as a
240    //    V8ObjectVar. This is to maintain backward compatibility with
241    //    synchronous scripting in Flash.
242    // 3) If the object is an array, return an ArrayVar.
243    // 4) If the object can be converted to a resource, return the ResourceVar.
244    // 5) Otherwise return a DictionaryVar.
245    scoped_ptr<blink::WebArrayBuffer> web_array_buffer(
246        blink::WebArrayBufferConverter::createFromV8Value(val, isolate));
247    if (web_array_buffer.get()) {
248      scoped_refptr<HostArrayBufferVar> buffer_var(
249          new HostArrayBufferVar(*web_array_buffer));
250      *result = buffer_var->GetPPVar();
251    } else if (object_vars_allowed == V8VarConverter::kAllowObjectVars) {
252      v8::Handle<v8::Object> object = val->ToObject();
253      *result = content::HostGlobals::Get()->
254          host_var_tracker()->V8ObjectVarForV8Object(instance, object);
255    } else if (val->IsArray()) {
256      *result = (new ArrayVar())->GetPPVar();
257    } else {
258      bool was_resource;
259      if (!resource_converter->FromV8Value(
260              val->ToObject(), context, result, &was_resource))
261        return false;
262      if (!was_resource) {
263        *result = (new DictionaryVar())->GetPPVar();
264      }
265    }
266  } else {
267    // Silently ignore the case where we can't convert to a Var as we may
268    // be trying to convert a type that doesn't have a corresponding
269    // PP_Var type.
270    return true;
271  }
272
273  *did_create = true;
274  if (val->IsObject() || val->IsString()) {
275    visited_handles->insert(
276        make_pair(HashedHandle(val->ToObject()),
277                  ScopedPPVar(ScopedPPVar::PassRef(), *result)));
278  }
279  return true;
280}
281
282bool CanHaveChildren(PP_Var var) {
283  return var.type == PP_VARTYPE_ARRAY || var.type == PP_VARTYPE_DICTIONARY;
284}
285
286}  // namespace
287
288V8VarConverter::V8VarConverter(PP_Instance instance)
289    : instance_(instance),
290      object_vars_allowed_(kDisallowObjectVars),
291      message_loop_proxy_(base::MessageLoopProxy::current()) {
292  resource_converter_.reset(new ResourceConverterImpl(
293      instance, RendererPpapiHost::GetForPPInstance(instance)));
294}
295
296V8VarConverter::V8VarConverter(PP_Instance instance,
297                               AllowObjectVars object_vars_allowed)
298    : instance_(instance),
299      object_vars_allowed_(object_vars_allowed),
300      message_loop_proxy_(base::MessageLoopProxy::current()) {
301  resource_converter_.reset(new ResourceConverterImpl(
302      instance, RendererPpapiHost::GetForPPInstance(instance)));
303}
304
305V8VarConverter::V8VarConverter(PP_Instance instance,
306                               scoped_ptr<ResourceConverter> resource_converter)
307    : instance_(instance),
308      object_vars_allowed_(kDisallowObjectVars),
309      message_loop_proxy_(base::MessageLoopProxy::current()),
310      resource_converter_(resource_converter.release()) {}
311
312V8VarConverter::~V8VarConverter() {}
313
314// To/FromV8Value use a stack-based DFS search to traverse V8/Var graph. Each
315// iteration, the top node on the stack examined. If the node has not been
316// visited yet (i.e. sentinel == false) then it is added to the list of parents
317// which contains all of the nodes on the path from the start node to the
318// current node. Each of the current nodes children are examined. If they appear
319// in the list of parents it means we have a cycle and we return NULL.
320// Otherwise, if they can have children, we add them to the stack. If the
321// node at the top of the stack has already been visited, then we pop it off the
322// stack and erase it from the list of parents.
323// static
324bool V8VarConverter::ToV8Value(const PP_Var& var,
325                               v8::Handle<v8::Context> context,
326                               v8::Handle<v8::Value>* result) {
327  v8::Context::Scope context_scope(context);
328  v8::Isolate* isolate = context->GetIsolate();
329  v8::EscapableHandleScope handle_scope(isolate);
330
331  VarHandleMap visited_ids;
332  ParentVarSet parent_ids;
333
334  std::stack<StackEntry<PP_Var> > stack;
335  stack.push(StackEntry<PP_Var>(var));
336  v8::Local<v8::Value> root;
337  bool is_root = true;
338
339  while (!stack.empty()) {
340    const PP_Var& current_var = stack.top().val;
341    v8::Handle<v8::Value> current_v8;
342
343    if (stack.top().sentinel) {
344      stack.pop();
345      if (CanHaveChildren(current_var))
346        parent_ids.erase(current_var.value.as_id);
347      continue;
348    } else {
349      stack.top().sentinel = true;
350    }
351
352    bool did_create = false;
353    if (!GetOrCreateV8Value(context,
354                            current_var,
355                            object_vars_allowed_,
356                            &current_v8,
357                            &did_create,
358                            &visited_ids,
359                            &parent_ids,
360                            resource_converter_.get())) {
361      return false;
362    }
363
364    if (is_root) {
365      is_root = false;
366      root = current_v8;
367    }
368
369    // Add child nodes to the stack.
370    if (current_var.type == PP_VARTYPE_ARRAY) {
371      parent_ids.insert(current_var.value.as_id);
372      ArrayVar* array_var = ArrayVar::FromPPVar(current_var);
373      if (!array_var) {
374        NOTREACHED();
375        return false;
376      }
377      DCHECK(current_v8->IsArray());
378      v8::Handle<v8::Array> v8_array = current_v8.As<v8::Array>();
379
380      for (size_t i = 0; i < array_var->elements().size(); ++i) {
381        const PP_Var& child_var = array_var->elements()[i].get();
382        v8::Handle<v8::Value> child_v8;
383        if (!GetOrCreateV8Value(context,
384                                child_var,
385                                object_vars_allowed_,
386                                &child_v8,
387                                &did_create,
388                                &visited_ids,
389                                &parent_ids,
390                                resource_converter_.get())) {
391          return false;
392        }
393        if (did_create && CanHaveChildren(child_var))
394          stack.push(child_var);
395        v8::TryCatch try_catch;
396        v8_array->Set(static_cast<uint32>(i), child_v8);
397        if (try_catch.HasCaught()) {
398          LOG(ERROR) << "Setter for index " << i << " threw an exception.";
399          return false;
400        }
401      }
402    } else if (current_var.type == PP_VARTYPE_DICTIONARY) {
403      parent_ids.insert(current_var.value.as_id);
404      DictionaryVar* dict_var = DictionaryVar::FromPPVar(current_var);
405      if (!dict_var) {
406        NOTREACHED();
407        return false;
408      }
409      DCHECK(current_v8->IsObject());
410      v8::Handle<v8::Object> v8_object = current_v8->ToObject();
411
412      for (DictionaryVar::KeyValueMap::const_iterator iter =
413               dict_var->key_value_map().begin();
414           iter != dict_var->key_value_map().end();
415           ++iter) {
416        const std::string& key = iter->first;
417        const PP_Var& child_var = iter->second.get();
418        v8::Handle<v8::Value> child_v8;
419        if (!GetOrCreateV8Value(context,
420                                child_var,
421                                object_vars_allowed_,
422                                &child_v8,
423                                &did_create,
424                                &visited_ids,
425                                &parent_ids,
426                                resource_converter_.get())) {
427          return false;
428        }
429        if (did_create && CanHaveChildren(child_var))
430          stack.push(child_var);
431        v8::TryCatch try_catch;
432        v8_object->Set(
433            v8::String::NewFromUtf8(
434                isolate, key.c_str(), v8::String::kNormalString, key.length()),
435            child_v8);
436        if (try_catch.HasCaught()) {
437          LOG(ERROR) << "Setter for property " << key.c_str() << " threw an "
438                     << "exception.";
439          return false;
440        }
441      }
442    }
443  }
444
445  *result = handle_scope.Escape(root);
446  return true;
447}
448
449V8VarConverter::VarResult V8VarConverter::FromV8Value(
450    v8::Handle<v8::Value> val,
451    v8::Handle<v8::Context> context,
452    const base::Callback<void(const ScopedPPVar&, bool)>& callback) {
453  VarResult result;
454  result.success = FromV8ValueInternal(val, context, &result.var);
455  if (!result.success)
456    resource_converter_->Reset();
457  result.completed_synchronously = !resource_converter_->NeedsFlush();
458  if (!result.completed_synchronously)
459    resource_converter_->Flush(base::Bind(callback, result.var));
460
461  return result;
462}
463
464bool V8VarConverter::FromV8ValueSync(
465    v8::Handle<v8::Value> val,
466    v8::Handle<v8::Context> context,
467    ppapi::ScopedPPVar* result_var) {
468  bool success = FromV8ValueInternal(val, context, result_var);
469  if (!success || resource_converter_->NeedsFlush()) {
470    resource_converter_->Reset();
471    return false;
472  }
473  return true;
474}
475
476bool V8VarConverter::FromV8ValueInternal(
477    v8::Handle<v8::Value> val,
478    v8::Handle<v8::Context> context,
479    ppapi::ScopedPPVar* result_var) {
480  v8::Context::Scope context_scope(context);
481  v8::HandleScope handle_scope(context->GetIsolate());
482
483  HandleVarMap visited_handles;
484  ParentHandleSet parent_handles;
485
486  std::stack<StackEntry<v8::Handle<v8::Value> > > stack;
487  stack.push(StackEntry<v8::Handle<v8::Value> >(val));
488  ScopedPPVar root;
489  *result_var = PP_MakeUndefined();
490  bool is_root = true;
491
492  while (!stack.empty()) {
493    v8::Handle<v8::Value> current_v8 = stack.top().val;
494    PP_Var current_var;
495
496    if (stack.top().sentinel) {
497      stack.pop();
498      if (current_v8->IsObject())
499        parent_handles.erase(HashedHandle(current_v8->ToObject()));
500      continue;
501    } else {
502      stack.top().sentinel = true;
503    }
504
505    bool did_create = false;
506    if (!GetOrCreateVar(current_v8,
507                        context,
508                        instance_,
509                        object_vars_allowed_,
510                        &current_var,
511                        &did_create,
512                        &visited_handles,
513                        &parent_handles,
514                        resource_converter_.get())) {
515      return false;
516    }
517
518    if (is_root) {
519      is_root = false;
520      root = current_var;
521    }
522
523    // Add child nodes to the stack.
524    if (current_var.type == PP_VARTYPE_ARRAY) {
525      DCHECK(current_v8->IsArray());
526      v8::Handle<v8::Array> v8_array = current_v8.As<v8::Array>();
527      parent_handles.insert(HashedHandle(v8_array));
528
529      ArrayVar* array_var = ArrayVar::FromPPVar(current_var);
530      if (!array_var) {
531        NOTREACHED();
532        return false;
533      }
534
535      for (uint32 i = 0; i < v8_array->Length(); ++i) {
536        v8::TryCatch try_catch;
537        v8::Handle<v8::Value> child_v8 = v8_array->Get(i);
538        if (try_catch.HasCaught())
539          return false;
540
541        if (!v8_array->HasRealIndexedProperty(i))
542          continue;
543
544        PP_Var child_var;
545        if (!GetOrCreateVar(child_v8,
546                            context,
547                            instance_,
548                            object_vars_allowed_,
549                            &child_var,
550                            &did_create,
551                            &visited_handles,
552                            &parent_handles,
553                            resource_converter_.get())) {
554          return false;
555        }
556        if (did_create && child_v8->IsObject())
557          stack.push(child_v8);
558
559        array_var->Set(i, child_var);
560      }
561    } else if (current_var.type == PP_VARTYPE_DICTIONARY) {
562      DCHECK(current_v8->IsObject());
563      v8::Handle<v8::Object> v8_object = current_v8->ToObject();
564      parent_handles.insert(HashedHandle(v8_object));
565
566      DictionaryVar* dict_var = DictionaryVar::FromPPVar(current_var);
567      if (!dict_var) {
568        NOTREACHED();
569        return false;
570      }
571
572      v8::Handle<v8::Array> property_names(v8_object->GetOwnPropertyNames());
573      for (uint32 i = 0; i < property_names->Length(); ++i) {
574        v8::Handle<v8::Value> key(property_names->Get(i));
575
576        // Extend this test to cover more types as necessary and if sensible.
577        if (!key->IsString() && !key->IsNumber()) {
578          NOTREACHED() << "Key \"" << *v8::String::Utf8Value(key)
579                       << "\" "
580                          "is neither a string nor a number";
581          return false;
582        }
583
584        // Skip all callbacks: crbug.com/139933
585        if (v8_object->HasRealNamedCallbackProperty(key->ToString()))
586          continue;
587
588        v8::String::Utf8Value name_utf8(key->ToString());
589
590        v8::TryCatch try_catch;
591        v8::Handle<v8::Value> child_v8 = v8_object->Get(key);
592        if (try_catch.HasCaught())
593          return false;
594
595        PP_Var child_var;
596        if (!GetOrCreateVar(child_v8,
597                            context,
598                            instance_,
599                            object_vars_allowed_,
600                            &child_var,
601                            &did_create,
602                            &visited_handles,
603                            &parent_handles,
604                            resource_converter_.get())) {
605          return false;
606        }
607        if (did_create && child_v8->IsObject())
608          stack.push(child_v8);
609
610        bool success = dict_var->SetWithStringKey(
611            std::string(*name_utf8, name_utf8.length()), child_var);
612        DCHECK(success);
613      }
614    }
615  }
616  *result_var = root;
617  return true;
618}
619
620}  // namespace content
621