- add sources.
[platform/framework/web/crosswalk.git] / src / content / renderer / pepper / v8_var_converter.cc
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 {
36   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 {
43   HashedHandle(v8::Handle<v8::Object> h) : handle(h) {}
44   size_t hash() const { return handle->GetIdentityHash(); }
45   bool operator==(const HashedHandle& h) const { return handle == h.handle; }
46   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> {
56   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.
84 bool GetOrCreateV8Value(const PP_Var& var,
85                         v8::Handle<v8::Value>* result,
86                         bool* did_create,
87                         VarHandleMap* visited_ids,
88                         ParentVarSet* parent_ids) {
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();
104       break;
105     case PP_VARTYPE_NULL:
106       *result = v8::Null();
107       break;
108     case PP_VARTYPE_BOOL:
109       *result = (var.value.as_bool == PP_TRUE) ? v8::True() : v8::False();
110       break;
111     case PP_VARTYPE_INT32:
112       *result = v8::Integer::New(var.value.as_int);
113       break;
114     case PP_VARTYPE_DOUBLE:
115       *result = v8::Number::New(var.value.as_double);
116       break;
117     case PP_VARTYPE_STRING: {
118       StringVar* string = StringVar::FromPPVar(var);
119       if (!string) {
120         NOTREACHED();
121         result->Clear();
122         return false;
123       }
124       const std::string& value = string->value();
125       // Create a string object rather than a string primitive. This allows us
126       // to have multiple references to the same string in javascript, which
127       // matches the reference behavior of PP_Vars.
128       *result = v8::String::New(value.c_str(), value.size())->ToObject();
129       break;
130     }
131     case PP_VARTYPE_ARRAY_BUFFER: {
132       ArrayBufferVar* buffer = ArrayBufferVar::FromPPVar(var);
133       if (!buffer) {
134         NOTREACHED();
135         result->Clear();
136         return false;
137       }
138       HostArrayBufferVar* host_buffer =
139           static_cast<HostArrayBufferVar*>(buffer);
140       *result = host_buffer->webkit_buffer().toV8Value();
141       break;
142     }
143     case PP_VARTYPE_ARRAY:
144       *result = v8::Array::New();
145       break;
146     case PP_VARTYPE_DICTIONARY:
147       *result = v8::Object::New();
148       break;
149     case PP_VARTYPE_OBJECT:
150     case PP_VARTYPE_RESOURCE:
151       // TODO(mgiuca): Convert PP_VARTYPE_RESOURCE vars into the correct V8
152       // type. (http://crbug.com/177017)
153       result->Clear();
154       return false;
155   }
156
157   *did_create = true;
158   if (ppapi::VarTracker::IsVarTypeRefcounted(var.type))
159     (*visited_ids)[var.value.as_id] = *result;
160   return true;
161 }
162
163 // For a given V8 value handle, this returns a PP_Var which corresponds to it.
164 // If the handle already exists in |visited_handles|, the PP_Var associated with
165 // it will be returned, otherwise a new V8 value will be created and added to
166 // the map. |did_create| indicates if a new PP_Var was created as a result of
167 // calling the function.
168 bool GetOrCreateVar(v8::Handle<v8::Value> val,
169                     v8::Handle<v8::Context> context,
170                     PP_Var* result,
171                     bool* did_create,
172                     HandleVarMap* visited_handles,
173                     ParentHandleSet* parent_handles,
174                     ResourceConverter* resource_converter) {
175   CHECK(!val.IsEmpty());
176   *did_create = false;
177
178   // Even though every v8 string primitive encountered will be a unique object,
179   // we still add them to |visited_handles| so that the corresponding string
180   // PP_Var created will be properly refcounted.
181   if (val->IsObject() || val->IsString()) {
182     if (parent_handles->count(HashedHandle(val->ToObject())) != 0)
183       return false;
184
185     HandleVarMap::const_iterator it = visited_handles->find(
186         HashedHandle(val->ToObject()));
187     if (it != visited_handles->end()) {
188       *result = it->second.get();
189       return true;
190     }
191   }
192
193   if (val->IsUndefined()) {
194     *result = PP_MakeUndefined();
195   } else if (val->IsNull()) {
196     *result = PP_MakeNull();
197   } else if (val->IsBoolean() || val->IsBooleanObject()) {
198     *result = PP_MakeBool(PP_FromBool(val->ToBoolean()->Value()));
199   } else if (val->IsInt32()) {
200     *result = PP_MakeInt32(val->ToInt32()->Value());
201   } else if (val->IsNumber() || val->IsNumberObject()) {
202     *result = PP_MakeDouble(val->ToNumber()->Value());
203   } else if (val->IsString() || val->IsStringObject()) {
204     v8::String::Utf8Value utf8(val->ToString());
205     *result = StringVar::StringToPPVar(std::string(*utf8, utf8.length()));
206   } else if (val->IsArray()) {
207     *result = (new ArrayVar())->GetPPVar();
208   } else if (val->IsObject()) {
209     scoped_ptr<WebKit::WebArrayBuffer> web_array_buffer(
210         WebKit::WebArrayBuffer::createFromV8Value(val));
211     if (web_array_buffer.get()) {
212       scoped_refptr<HostArrayBufferVar> buffer_var(new HostArrayBufferVar(
213           *web_array_buffer));
214       *result = buffer_var->GetPPVar();
215     } else {
216       bool was_resource;
217       if (!resource_converter->FromV8Value(val->ToObject(), context, result,
218                                            &was_resource))
219         return false;
220       if (!was_resource) {
221         *result = (new DictionaryVar())->GetPPVar();
222       }
223     }
224   } else {
225     // Silently ignore the case where we can't convert to a Var as we may
226     // be trying to convert a type that doesn't have a corresponding
227     // PP_Var type.
228     return true;
229   }
230
231   *did_create = true;
232   if (val->IsObject() || val->IsString()) {
233     visited_handles->insert(make_pair(
234         HashedHandle(val->ToObject()),
235         ScopedPPVar(ScopedPPVar::PassRef(), *result)));
236   }
237   return true;
238 }
239
240 bool CanHaveChildren(PP_Var var) {
241   return var.type == PP_VARTYPE_ARRAY || var.type == PP_VARTYPE_DICTIONARY;
242 }
243
244 }  // namespace
245
246 V8VarConverter::V8VarConverter(PP_Instance instance)
247     : message_loop_proxy_(base::MessageLoopProxy::current()) {
248   resource_converter_.reset(new ResourceConverterImpl(
249       instance, RendererPpapiHost::GetForPPInstance(instance)));
250 }
251
252 V8VarConverter::V8VarConverter(
253     PP_Instance instance,
254     scoped_ptr<ResourceConverter> resource_converter)
255     : message_loop_proxy_(base::MessageLoopProxy::current()),
256       resource_converter_(resource_converter.release()) {
257 }
258
259 V8VarConverter::~V8VarConverter() {
260 }
261
262 // To/FromV8Value use a stack-based DFS search to traverse V8/Var graph. Each
263 // iteration, the top node on the stack examined. If the node has not been
264 // visited yet (i.e. sentinel == false) then it is added to the list of parents
265 // which contains all of the nodes on the path from the start node to the
266 // current node. Each of the current nodes children are examined. If they appear
267 // in the list of parents it means we have a cycle and we return NULL.
268 // Otherwise, if they can have children, we add them to the stack. If the
269 // node at the top of the stack has already been visited, then we pop it off the
270 // stack and erase it from the list of parents.
271 // static
272 bool V8VarConverter::ToV8Value(const PP_Var& var,
273                                v8::Handle<v8::Context> context,
274                                v8::Handle<v8::Value>* result) {
275   v8::Context::Scope context_scope(context);
276   v8::HandleScope handle_scope(context->GetIsolate());
277
278   VarHandleMap visited_ids;
279   ParentVarSet parent_ids;
280
281   std::stack<StackEntry<PP_Var> > stack;
282   stack.push(StackEntry<PP_Var>(var));
283   v8::Handle<v8::Value> root;
284   bool is_root = true;
285
286   while (!stack.empty()) {
287     const PP_Var& current_var = stack.top().val;
288     v8::Handle<v8::Value> current_v8;
289
290     if (stack.top().sentinel) {
291       stack.pop();
292       if (CanHaveChildren(current_var))
293         parent_ids.erase(current_var.value.as_id);
294       continue;
295     } else {
296       stack.top().sentinel = true;
297     }
298
299     bool did_create = false;
300     if (!GetOrCreateV8Value(current_var, &current_v8, &did_create,
301                             &visited_ids, &parent_ids)) {
302       return false;
303     }
304
305     if (is_root) {
306       is_root = false;
307       root = current_v8;
308     }
309
310     // Add child nodes to the stack.
311     if (current_var.type == PP_VARTYPE_ARRAY) {
312       parent_ids.insert(current_var.value.as_id);
313       ArrayVar* array_var = ArrayVar::FromPPVar(current_var);
314       if (!array_var) {
315         NOTREACHED();
316         return false;
317       }
318       DCHECK(current_v8->IsArray());
319       v8::Handle<v8::Array> v8_array = current_v8.As<v8::Array>();
320
321       for (size_t i = 0; i < array_var->elements().size(); ++i) {
322         const PP_Var& child_var = array_var->elements()[i].get();
323         v8::Handle<v8::Value> child_v8;
324         if (!GetOrCreateV8Value(child_var, &child_v8, &did_create,
325                                 &visited_ids, &parent_ids)) {
326           return false;
327         }
328         if (did_create && CanHaveChildren(child_var))
329           stack.push(child_var);
330         v8::TryCatch try_catch;
331         v8_array->Set(static_cast<uint32>(i), child_v8);
332         if (try_catch.HasCaught()) {
333           LOG(ERROR) << "Setter for index " << i << " threw an exception.";
334           return false;
335         }
336       }
337     } else if (current_var.type == PP_VARTYPE_DICTIONARY) {
338       parent_ids.insert(current_var.value.as_id);
339       DictionaryVar* dict_var = DictionaryVar::FromPPVar(current_var);
340       if (!dict_var) {
341         NOTREACHED();
342         return false;
343       }
344       DCHECK(current_v8->IsObject());
345       v8::Handle<v8::Object> v8_object = current_v8->ToObject();
346
347       for (DictionaryVar::KeyValueMap::const_iterator iter =
348                dict_var->key_value_map().begin();
349            iter != dict_var->key_value_map().end();
350            ++iter) {
351         const std::string& key = iter->first;
352         const PP_Var& child_var = iter->second.get();
353         v8::Handle<v8::Value> child_v8;
354         if (!GetOrCreateV8Value(child_var, &child_v8, &did_create,
355                                 &visited_ids, &parent_ids)) {
356           return false;
357         }
358         if (did_create && CanHaveChildren(child_var))
359           stack.push(child_var);
360         v8::TryCatch try_catch;
361         v8_object->Set(v8::String::New(key.c_str(), key.length()), child_v8);
362         if (try_catch.HasCaught()) {
363           LOG(ERROR) << "Setter for property " << key.c_str() << " threw an "
364               << "exception.";
365           return false;
366         }
367       }
368     }
369   }
370
371   *result = handle_scope.Close(root);
372   return true;
373 }
374
375 void V8VarConverter::FromV8Value(
376     v8::Handle<v8::Value> val,
377     v8::Handle<v8::Context> context,
378     const base::Callback<void(const ScopedPPVar&, bool)>& callback) {
379   v8::Context::Scope context_scope(context);
380   v8::HandleScope handle_scope(context->GetIsolate());
381
382   HandleVarMap visited_handles;
383   ParentHandleSet parent_handles;
384
385   std::stack<StackEntry<v8::Handle<v8::Value> > > stack;
386   stack.push(StackEntry<v8::Handle<v8::Value> >(val));
387   ScopedPPVar root;
388   bool is_root = true;
389
390   while (!stack.empty()) {
391     v8::Handle<v8::Value> current_v8 = stack.top().val;
392     PP_Var current_var;
393
394     if (stack.top().sentinel) {
395       stack.pop();
396       if (current_v8->IsObject())
397         parent_handles.erase(HashedHandle(current_v8->ToObject()));
398       continue;
399     } else {
400       stack.top().sentinel = true;
401     }
402
403     bool did_create = false;
404     if (!GetOrCreateVar(current_v8, context, &current_var, &did_create,
405                         &visited_handles, &parent_handles,
406                         resource_converter_.get())) {
407       message_loop_proxy_->PostTask(FROM_HERE,
408           base::Bind(callback, ScopedPPVar(PP_MakeUndefined()), false));
409       return;
410     }
411
412     if (is_root) {
413       is_root = false;
414       root = current_var;
415     }
416
417     // Add child nodes to the stack.
418     if (current_var.type == PP_VARTYPE_ARRAY) {
419       DCHECK(current_v8->IsArray());
420       v8::Handle<v8::Array> v8_array = current_v8.As<v8::Array>();
421       parent_handles.insert(HashedHandle(v8_array));
422
423       ArrayVar* array_var = ArrayVar::FromPPVar(current_var);
424       if (!array_var) {
425         NOTREACHED();
426         message_loop_proxy_->PostTask(FROM_HERE,
427             base::Bind(callback, ScopedPPVar(PP_MakeUndefined()), false));
428         return;
429       }
430
431       for (uint32 i = 0; i < v8_array->Length(); ++i) {
432         v8::TryCatch try_catch;
433         v8::Handle<v8::Value> child_v8 = v8_array->Get(i);
434         if (try_catch.HasCaught()) {
435           message_loop_proxy_->PostTask(FROM_HERE,
436               base::Bind(callback, ScopedPPVar(PP_MakeUndefined()), false));
437           return;
438         }
439
440         if (!v8_array->HasRealIndexedProperty(i))
441           continue;
442
443         PP_Var child_var;
444         if (!GetOrCreateVar(child_v8, context, &child_var, &did_create,
445                             &visited_handles, &parent_handles,
446                             resource_converter_.get())) {
447           message_loop_proxy_->PostTask(FROM_HERE,
448               base::Bind(callback, ScopedPPVar(PP_MakeUndefined()), false));
449           return;
450         }
451         if (did_create && child_v8->IsObject())
452           stack.push(child_v8);
453
454         array_var->Set(i, child_var);
455       }
456     } else if (current_var.type == PP_VARTYPE_DICTIONARY) {
457       DCHECK(current_v8->IsObject());
458       v8::Handle<v8::Object> v8_object = current_v8->ToObject();
459       parent_handles.insert(HashedHandle(v8_object));
460
461       DictionaryVar* dict_var = DictionaryVar::FromPPVar(current_var);
462       if (!dict_var) {
463         NOTREACHED();
464         message_loop_proxy_->PostTask(FROM_HERE,
465             base::Bind(callback, ScopedPPVar(PP_MakeUndefined()), false));
466         return;
467       }
468
469       v8::Handle<v8::Array> property_names(v8_object->GetOwnPropertyNames());
470       for (uint32 i = 0; i < property_names->Length(); ++i) {
471         v8::Handle<v8::Value> key(property_names->Get(i));
472
473         // Extend this test to cover more types as necessary and if sensible.
474         if (!key->IsString() && !key->IsNumber()) {
475           NOTREACHED() << "Key \"" << *v8::String::AsciiValue(key) << "\" "
476                           "is neither a string nor a number";
477           message_loop_proxy_->PostTask(FROM_HERE,
478               base::Bind(callback, ScopedPPVar(PP_MakeUndefined()), false));
479           return;
480         }
481
482         // Skip all callbacks: crbug.com/139933
483         if (v8_object->HasRealNamedCallbackProperty(key->ToString()))
484           continue;
485
486         v8::String::Utf8Value name_utf8(key->ToString());
487
488         v8::TryCatch try_catch;
489         v8::Handle<v8::Value> child_v8 = v8_object->Get(key);
490         if (try_catch.HasCaught()) {
491           message_loop_proxy_->PostTask(FROM_HERE,
492               base::Bind(callback, ScopedPPVar(PP_MakeUndefined()), false));
493           return;
494         }
495
496         PP_Var child_var;
497         if (!GetOrCreateVar(child_v8, context, &child_var, &did_create,
498                             &visited_handles, &parent_handles,
499                             resource_converter_.get())) {
500           message_loop_proxy_->PostTask(FROM_HERE,
501               base::Bind(callback, ScopedPPVar(PP_MakeUndefined()), false));
502           return;
503         }
504         if (did_create && child_v8->IsObject())
505           stack.push(child_v8);
506
507         bool success = dict_var->SetWithStringKey(
508             std::string(*name_utf8, name_utf8.length()), child_var);
509         DCHECK(success);
510       }
511     }
512   }
513   resource_converter_->Flush(base::Bind(callback, root));
514 }
515
516 }  // namespace content