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
21using ppapi::ArrayBufferVar;
22using ppapi::ArrayVar;
23using ppapi::DictionaryVar;
24using ppapi::ScopedPPVar;
25using ppapi::StringVar;
26using std::make_pair;
27
28namespace {
29
30template <class T>
31struct 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
38struct 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
48namespace BASE_HASH_NAMESPACE {
49#if defined(COMPILER_GCC)
50template <>
51struct hash<HashedHandle> {
52  size_t operator()(const HashedHandle& handle) const {
53    return handle.hash();
54  }
55};
56#elif defined(COMPILER_MSVC)
57inline size_t hash_value(const HashedHandle& handle) {
58  return handle.hash();
59}
60#endif
61}  // namespace BASE_HASH_NAMESPACE
62
63namespace content {
64namespace V8VarConverter {
65
66namespace {
67
68// Maps PP_Var IDs to the V8 value handle they correspond to.
69typedef base::hash_map<int64_t, v8::Handle<v8::Value> > VarHandleMap;
70typedef base::hash_set<int64_t> ParentVarSet;
71
72// Maps V8 value handles to the PP_Var they correspond to.
73typedef base::hash_map<HashedHandle, ScopedPPVar> HandleVarMap;
74typedef 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.
81bool 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.
164bool 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
228bool 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
244bool 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
347bool 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