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