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