Updating trunk VERSION from 2139.0 to 2140.0
[chromium-blink-merge.git] / content / renderer / pepper / v8_var_converter.cc
blob5fdd87f3e2e97ec5d9082940b61f069c348d1c07
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.
5 #include "content/renderer/pepper/v8_var_converter.h"
7 #include <map>
8 #include <stack>
9 #include <string>
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"
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;
36 namespace {
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;
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 bool operator<(const HashedHandle& h) const { return hash() < h.hash(); }
51 v8::Handle<v8::Object> handle;
54 } // namespace
56 namespace BASE_HASH_NAMESPACE {
57 #if defined(COMPILER_GCC)
58 template <>
59 struct hash<HashedHandle> {
60 size_t operator()(const HashedHandle& handle) const { return handle.hash(); }
62 #elif defined(COMPILER_MSVC)
63 inline size_t hash_value(const HashedHandle& handle) { return handle.hash(); }
64 #endif
65 } // namespace BASE_HASH_NAMESPACE
67 namespace content {
69 namespace {
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;
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;
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(v8::Handle<v8::Context> context,
85 const PP_Var& var,
86 bool 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;
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;
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;
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;
138 case PP_VARTYPE_ARRAY_BUFFER: {
139 ArrayBufferVar* buffer = ArrayBufferVar::FromPPVar(var);
140 if (!buffer) {
141 NOTREACHED();
142 result->Clear();
143 return false;
145 HostArrayBufferVar* host_buffer =
146 static_cast<HostArrayBufferVar*>(buffer);
147 *result = blink::WebArrayBufferConverter::toV8Value(
148 &host_buffer->webkit_buffer(), context->Global(), isolate);
149 break;
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 DCHECK(object_vars_allowed);
159 scoped_refptr<V8ObjectVar> v8_object_var = V8ObjectVar::FromPPVar(var);
160 if (!v8_object_var.get()) {
161 NOTREACHED();
162 result->Clear();
163 return false;
165 *result = v8_object_var->GetHandle();
166 break;
168 case PP_VARTYPE_RESOURCE:
169 if (!resource_converter->ToV8Value(var, context, result)) {
170 result->Clear();
171 return false;
173 break;
176 *did_create = true;
177 if (ppapi::VarTracker::IsVarTypeRefcounted(var.type))
178 (*visited_ids)[var.value.as_id] = *result;
179 return true;
182 // For a given V8 value handle, this returns a PP_Var which corresponds to it.
183 // If the handle already exists in |visited_handles|, the PP_Var associated with
184 // it will be returned, otherwise a new V8 value will be created and added to
185 // the map. |did_create| indicates if a new PP_Var was created as a result of
186 // calling the function.
187 bool GetOrCreateVar(v8::Handle<v8::Value> val,
188 v8::Handle<v8::Context> context,
189 PP_Instance instance,
190 bool object_vars_allowed,
191 PP_Var* result,
192 bool* did_create,
193 HandleVarMap* visited_handles,
194 ParentHandleSet* parent_handles,
195 ResourceConverter* resource_converter) {
196 CHECK(!val.IsEmpty());
197 *did_create = false;
199 // Even though every v8 string primitive encountered will be a unique object,
200 // we still add them to |visited_handles| so that the corresponding string
201 // PP_Var created will be properly refcounted.
202 if (val->IsObject() || val->IsString()) {
203 if (parent_handles->count(HashedHandle(val->ToObject())) != 0)
204 return false;
206 HandleVarMap::const_iterator it =
207 visited_handles->find(HashedHandle(val->ToObject()));
208 if (it != visited_handles->end()) {
209 *result = it->second.get();
210 return true;
214 v8::Isolate* isolate = context->GetIsolate();
215 if (val->IsUndefined()) {
216 *result = PP_MakeUndefined();
217 } else if (val->IsNull()) {
218 *result = PP_MakeNull();
219 } else if (val->IsBoolean() || val->IsBooleanObject()) {
220 *result = PP_MakeBool(PP_FromBool(val->ToBoolean()->Value()));
221 } else if (val->IsInt32()) {
222 *result = PP_MakeInt32(val->ToInt32()->Value());
223 } else if (val->IsNumber() || val->IsNumberObject()) {
224 *result = PP_MakeDouble(val->ToNumber()->Value());
225 } else if (val->IsString() || val->IsStringObject()) {
226 v8::String::Utf8Value utf8(val->ToString());
227 *result = StringVar::StringToPPVar(std::string(*utf8, utf8.length()));
228 } else if (val->IsArray()) {
229 *result = (new ArrayVar())->GetPPVar();
230 } else if (val->IsObject()) {
231 scoped_ptr<blink::WebArrayBuffer> web_array_buffer(
232 blink::WebArrayBufferConverter::createFromV8Value(val, isolate));
233 if (web_array_buffer.get()) {
234 scoped_refptr<HostArrayBufferVar> buffer_var(
235 new HostArrayBufferVar(*web_array_buffer));
236 *result = buffer_var->GetPPVar();
237 } else if (object_vars_allowed) {
238 v8::Handle<v8::Object> object = val->ToObject();
239 *result = content::HostGlobals::Get()->
240 host_var_tracker()->V8ObjectVarForV8Object(instance, object);
241 } else {
242 bool was_resource;
243 if (!resource_converter->FromV8Value(
244 val->ToObject(), context, result, &was_resource))
245 return false;
246 if (!was_resource) {
247 *result = (new DictionaryVar())->GetPPVar();
250 } else {
251 // Silently ignore the case where we can't convert to a Var as we may
252 // be trying to convert a type that doesn't have a corresponding
253 // PP_Var type.
254 return true;
257 *did_create = true;
258 if (val->IsObject() || val->IsString()) {
259 visited_handles->insert(
260 make_pair(HashedHandle(val->ToObject()),
261 ScopedPPVar(ScopedPPVar::PassRef(), *result)));
263 return true;
266 bool CanHaveChildren(PP_Var var) {
267 return var.type == PP_VARTYPE_ARRAY || var.type == PP_VARTYPE_DICTIONARY;
270 } // namespace
272 V8VarConverter::V8VarConverter(PP_Instance instance)
273 : instance_(instance),
274 object_vars_allowed_(false),
275 message_loop_proxy_(base::MessageLoopProxy::current()) {
276 resource_converter_.reset(new ResourceConverterImpl(
277 instance, RendererPpapiHost::GetForPPInstance(instance)));
280 V8VarConverter::V8VarConverter(PP_Instance instance, bool object_vars_allowed)
281 : instance_(instance),
282 object_vars_allowed_(object_vars_allowed),
283 message_loop_proxy_(base::MessageLoopProxy::current()) {
284 resource_converter_.reset(new ResourceConverterImpl(
285 instance, RendererPpapiHost::GetForPPInstance(instance)));
288 V8VarConverter::V8VarConverter(PP_Instance instance,
289 scoped_ptr<ResourceConverter> resource_converter)
290 : instance_(instance),
291 object_vars_allowed_(false),
292 message_loop_proxy_(base::MessageLoopProxy::current()),
293 resource_converter_(resource_converter.release()) {}
295 V8VarConverter::~V8VarConverter() {}
297 // To/FromV8Value use a stack-based DFS search to traverse V8/Var graph. Each
298 // iteration, the top node on the stack examined. If the node has not been
299 // visited yet (i.e. sentinel == false) then it is added to the list of parents
300 // which contains all of the nodes on the path from the start node to the
301 // current node. Each of the current nodes children are examined. If they appear
302 // in the list of parents it means we have a cycle and we return NULL.
303 // Otherwise, if they can have children, we add them to the stack. If the
304 // node at the top of the stack has already been visited, then we pop it off the
305 // stack and erase it from the list of parents.
306 // static
307 bool V8VarConverter::ToV8Value(const PP_Var& var,
308 v8::Handle<v8::Context> context,
309 v8::Handle<v8::Value>* result) {
310 v8::Context::Scope context_scope(context);
311 v8::Isolate* isolate = context->GetIsolate();
312 v8::EscapableHandleScope handle_scope(isolate);
314 VarHandleMap visited_ids;
315 ParentVarSet parent_ids;
317 std::stack<StackEntry<PP_Var> > stack;
318 stack.push(StackEntry<PP_Var>(var));
319 v8::Local<v8::Value> root;
320 bool is_root = true;
322 while (!stack.empty()) {
323 const PP_Var& current_var = stack.top().val;
324 v8::Handle<v8::Value> current_v8;
326 if (stack.top().sentinel) {
327 stack.pop();
328 if (CanHaveChildren(current_var))
329 parent_ids.erase(current_var.value.as_id);
330 continue;
331 } else {
332 stack.top().sentinel = true;
335 bool did_create = false;
336 if (!GetOrCreateV8Value(context,
337 current_var,
338 object_vars_allowed_,
339 &current_v8,
340 &did_create,
341 &visited_ids,
342 &parent_ids,
343 resource_converter_.get())) {
344 return false;
347 if (is_root) {
348 is_root = false;
349 root = current_v8;
352 // Add child nodes to the stack.
353 if (current_var.type == PP_VARTYPE_ARRAY) {
354 parent_ids.insert(current_var.value.as_id);
355 ArrayVar* array_var = ArrayVar::FromPPVar(current_var);
356 if (!array_var) {
357 NOTREACHED();
358 return false;
360 DCHECK(current_v8->IsArray());
361 v8::Handle<v8::Array> v8_array = current_v8.As<v8::Array>();
363 for (size_t i = 0; i < array_var->elements().size(); ++i) {
364 const PP_Var& child_var = array_var->elements()[i].get();
365 v8::Handle<v8::Value> child_v8;
366 if (!GetOrCreateV8Value(context,
367 child_var,
368 object_vars_allowed_,
369 &child_v8,
370 &did_create,
371 &visited_ids,
372 &parent_ids,
373 resource_converter_.get())) {
374 return false;
376 if (did_create && CanHaveChildren(child_var))
377 stack.push(child_var);
378 v8::TryCatch try_catch;
379 v8_array->Set(static_cast<uint32>(i), child_v8);
380 if (try_catch.HasCaught()) {
381 LOG(ERROR) << "Setter for index " << i << " threw an exception.";
382 return false;
385 } else if (current_var.type == PP_VARTYPE_DICTIONARY) {
386 parent_ids.insert(current_var.value.as_id);
387 DictionaryVar* dict_var = DictionaryVar::FromPPVar(current_var);
388 if (!dict_var) {
389 NOTREACHED();
390 return false;
392 DCHECK(current_v8->IsObject());
393 v8::Handle<v8::Object> v8_object = current_v8->ToObject();
395 for (DictionaryVar::KeyValueMap::const_iterator iter =
396 dict_var->key_value_map().begin();
397 iter != dict_var->key_value_map().end();
398 ++iter) {
399 const std::string& key = iter->first;
400 const PP_Var& child_var = iter->second.get();
401 v8::Handle<v8::Value> child_v8;
402 if (!GetOrCreateV8Value(context,
403 child_var,
404 object_vars_allowed_,
405 &child_v8,
406 &did_create,
407 &visited_ids,
408 &parent_ids,
409 resource_converter_.get())) {
410 return false;
412 if (did_create && CanHaveChildren(child_var))
413 stack.push(child_var);
414 v8::TryCatch try_catch;
415 v8_object->Set(
416 v8::String::NewFromUtf8(
417 isolate, key.c_str(), v8::String::kNormalString, key.length()),
418 child_v8);
419 if (try_catch.HasCaught()) {
420 LOG(ERROR) << "Setter for property " << key.c_str() << " threw an "
421 << "exception.";
422 return false;
428 *result = handle_scope.Escape(root);
429 return true;
432 V8VarConverter::VarResult V8VarConverter::FromV8Value(
433 v8::Handle<v8::Value> val,
434 v8::Handle<v8::Context> context,
435 const base::Callback<void(const ScopedPPVar&, bool)>& callback) {
436 VarResult result;
437 result.success = FromV8ValueInternal(val, context, &result.var);
438 if (!result.success)
439 resource_converter_->Reset();
440 result.completed_synchronously = !resource_converter_->NeedsFlush();
441 if (!result.completed_synchronously)
442 resource_converter_->Flush(base::Bind(callback, result.var));
444 return result;
447 bool V8VarConverter::FromV8ValueSync(
448 v8::Handle<v8::Value> val,
449 v8::Handle<v8::Context> context,
450 ppapi::ScopedPPVar* result_var) {
451 bool success = FromV8ValueInternal(val, context, result_var);
452 if (!success || resource_converter_->NeedsFlush()) {
453 resource_converter_->Reset();
454 return false;
456 return true;
459 bool V8VarConverter::FromV8ValueInternal(
460 v8::Handle<v8::Value> val,
461 v8::Handle<v8::Context> context,
462 ppapi::ScopedPPVar* result_var) {
463 v8::Context::Scope context_scope(context);
464 v8::HandleScope handle_scope(context->GetIsolate());
466 HandleVarMap visited_handles;
467 ParentHandleSet parent_handles;
469 std::stack<StackEntry<v8::Handle<v8::Value> > > stack;
470 stack.push(StackEntry<v8::Handle<v8::Value> >(val));
471 ScopedPPVar root;
472 *result_var = PP_MakeUndefined();
473 bool is_root = true;
475 while (!stack.empty()) {
476 v8::Handle<v8::Value> current_v8 = stack.top().val;
477 PP_Var current_var;
479 if (stack.top().sentinel) {
480 stack.pop();
481 if (current_v8->IsObject())
482 parent_handles.erase(HashedHandle(current_v8->ToObject()));
483 continue;
484 } else {
485 stack.top().sentinel = true;
488 bool did_create = false;
489 if (!GetOrCreateVar(current_v8,
490 context,
491 instance_,
492 object_vars_allowed_,
493 &current_var,
494 &did_create,
495 &visited_handles,
496 &parent_handles,
497 resource_converter_.get())) {
498 return false;
501 if (is_root) {
502 is_root = false;
503 root = current_var;
506 // Add child nodes to the stack.
507 if (current_var.type == PP_VARTYPE_ARRAY) {
508 DCHECK(current_v8->IsArray());
509 v8::Handle<v8::Array> v8_array = current_v8.As<v8::Array>();
510 parent_handles.insert(HashedHandle(v8_array));
512 ArrayVar* array_var = ArrayVar::FromPPVar(current_var);
513 if (!array_var) {
514 NOTREACHED();
515 return false;
518 for (uint32 i = 0; i < v8_array->Length(); ++i) {
519 v8::TryCatch try_catch;
520 v8::Handle<v8::Value> child_v8 = v8_array->Get(i);
521 if (try_catch.HasCaught())
522 return false;
524 if (!v8_array->HasRealIndexedProperty(i))
525 continue;
527 PP_Var child_var;
528 if (!GetOrCreateVar(child_v8,
529 context,
530 instance_,
531 object_vars_allowed_,
532 &child_var,
533 &did_create,
534 &visited_handles,
535 &parent_handles,
536 resource_converter_.get())) {
537 return false;
539 if (did_create && child_v8->IsObject())
540 stack.push(child_v8);
542 array_var->Set(i, child_var);
544 } else if (current_var.type == PP_VARTYPE_DICTIONARY) {
545 DCHECK(current_v8->IsObject());
546 v8::Handle<v8::Object> v8_object = current_v8->ToObject();
547 parent_handles.insert(HashedHandle(v8_object));
549 DictionaryVar* dict_var = DictionaryVar::FromPPVar(current_var);
550 if (!dict_var) {
551 NOTREACHED();
552 return false;
555 v8::Handle<v8::Array> property_names(v8_object->GetOwnPropertyNames());
556 for (uint32 i = 0; i < property_names->Length(); ++i) {
557 v8::Handle<v8::Value> key(property_names->Get(i));
559 // Extend this test to cover more types as necessary and if sensible.
560 if (!key->IsString() && !key->IsNumber()) {
561 NOTREACHED() << "Key \"" << *v8::String::Utf8Value(key)
562 << "\" "
563 "is neither a string nor a number";
564 return false;
567 // Skip all callbacks: crbug.com/139933
568 if (v8_object->HasRealNamedCallbackProperty(key->ToString()))
569 continue;
571 v8::String::Utf8Value name_utf8(key->ToString());
573 v8::TryCatch try_catch;
574 v8::Handle<v8::Value> child_v8 = v8_object->Get(key);
575 if (try_catch.HasCaught())
576 return false;
578 PP_Var child_var;
579 if (!GetOrCreateVar(child_v8,
580 context,
581 instance_,
582 object_vars_allowed_,
583 &child_var,
584 &did_create,
585 &visited_handles,
586 &parent_handles,
587 resource_converter_.get())) {
588 return false;
590 if (did_create && child_v8->IsObject())
591 stack.push(child_v8);
593 bool success = dict_var->SetWithStringKey(
594 std::string(*name_utf8, name_utf8.length()), child_var);
595 DCHECK(success);
599 *result_var = root;
600 return true;
603 } // namespace content