Re-subimission of https://codereview.chromium.org/1041213003/
[chromium-blink-merge.git] / content / renderer / pepper / v8_var_converter.cc
blobd68395f9c35df1f54c0631212775b44ac1556457
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/web/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 v8::Handle<v8::Object> handle;
53 } // namespace
55 namespace BASE_HASH_NAMESPACE {
56 template <>
57 struct hash<HashedHandle> {
58 size_t operator()(const HashedHandle& handle) const { return handle.hash(); }
60 } // namespace BASE_HASH_NAMESPACE
62 namespace content {
64 namespace {
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;
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;
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;
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;
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;
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;
133 case PP_VARTYPE_ARRAY_BUFFER: {
134 ArrayBufferVar* buffer = ArrayBufferVar::FromPPVar(var);
135 if (!buffer) {
136 NOTREACHED();
137 result->Clear();
138 return false;
140 HostArrayBufferVar* host_buffer =
141 static_cast<HostArrayBufferVar*>(buffer);
142 *result = blink::WebArrayBufferConverter::toV8Value(
143 &host_buffer->webkit_buffer(), context->Global(), isolate);
144 break;
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;
162 scoped_refptr<V8ObjectVar> v8_object_var = V8ObjectVar::FromPPVar(var);
163 if (!v8_object_var.get()) {
164 NOTREACHED();
165 result->Clear();
166 return false;
168 *result = v8_object_var->GetHandle();
169 break;
171 case PP_VARTYPE_RESOURCE:
172 if (!resource_converter->ToV8Value(var, context, result)) {
173 result->Clear();
174 return false;
176 break;
179 *did_create = true;
180 if (ppapi::VarTracker::IsVarTypeRefcounted(var.type))
181 (*visited_ids)[var.value.as_id] = *result;
182 return true;
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;
202 v8::Isolate* isolate = context->GetIsolate();
203 // Even though every v8 string primitive encountered will be a unique object,
204 // we still add them to |visited_handles| so that the corresponding string
205 // PP_Var created will be properly refcounted.
206 if (val->IsObject() || val->IsString()) {
207 if (parent_handles->count(HashedHandle(val->ToObject(isolate))) != 0)
208 return false;
210 HandleVarMap::const_iterator it =
211 visited_handles->find(HashedHandle(val->ToObject(isolate)));
212 if (it != visited_handles->end()) {
213 *result = it->second.get();
214 return true;
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(isolate)->Value()));
224 } else if (val->IsInt32()) {
225 *result = PP_MakeInt32(val->ToInt32(isolate)->Value());
226 } else if (val->IsNumber() || val->IsNumberObject()) {
227 *result = PP_MakeDouble(val->ToNumber(isolate)->Value());
228 } else if (val->IsString() || val->IsStringObject()) {
229 v8::String::Utf8Value utf8(val->ToString(isolate));
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.As<v8::Object>();
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(val.As<v8::Object>(), context,
255 result, &was_resource))
256 return false;
257 if (!was_resource) {
258 *result = (new DictionaryVar())->GetPPVar();
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;
268 *did_create = true;
269 if (val->IsObject() || val->IsString()) {
270 visited_handles->insert(
271 make_pair(HashedHandle(val->ToObject(isolate)),
272 ScopedPPVar(ScopedPPVar::PassRef(), *result)));
274 return true;
277 bool CanHaveChildren(PP_Var var) {
278 return var.type == PP_VARTYPE_ARRAY || var.type == PP_VARTYPE_DICTIONARY;
281 } // namespace
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));
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()) {}
296 V8VarConverter::~V8VarConverter() {}
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);
315 VarHandleMap visited_ids;
316 ParentVarSet parent_ids;
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;
323 while (!stack.empty()) {
324 const PP_Var& current_var = stack.top().val;
325 v8::Handle<v8::Value> current_v8;
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;
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;
348 if (is_root) {
349 is_root = false;
350 root = current_v8;
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;
361 DCHECK(current_v8->IsArray());
362 v8::Handle<v8::Array> v8_array = current_v8.As<v8::Array>();
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;
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;
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;
393 DCHECK(current_v8->IsObject());
394 v8::Handle<v8::Object> v8_object = current_v8.As<v8::Object>();
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;
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;
429 *result = handle_scope.Escape(root);
430 return true;
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));
445 return result;
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;
457 return true;
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());
467 HandleVarMap visited_handles;
468 ParentHandleSet parent_handles;
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;
476 while (!stack.empty()) {
477 v8::Handle<v8::Value> current_v8 = stack.top().val;
478 PP_Var current_var;
480 if (stack.top().sentinel) {
481 stack.pop();
482 if (current_v8->IsObject())
483 parent_handles.erase(HashedHandle(current_v8.As<v8::Object>()));
484 continue;
485 } else {
486 stack.top().sentinel = true;
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;
502 if (is_root) {
503 is_root = false;
504 root = current_var;
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));
513 ArrayVar* array_var = ArrayVar::FromPPVar(current_var);
514 if (!array_var) {
515 NOTREACHED();
516 return false;
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;
525 if (!v8_array->HasRealIndexedProperty(i))
526 continue;
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;
540 if (did_create && child_v8->IsObject())
541 stack.push(child_v8);
543 array_var->Set(i, child_var);
545 } else if (current_var.type == PP_VARTYPE_DICTIONARY) {
546 DCHECK(current_v8->IsObject());
547 v8::Handle<v8::Object> v8_object = current_v8.As<v8::Object>();
548 parent_handles.insert(HashedHandle(v8_object));
550 DictionaryVar* dict_var = DictionaryVar::FromPPVar(current_var);
551 if (!dict_var) {
552 NOTREACHED();
553 return false;
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));
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;
568 v8::Handle<v8::String> key_string =
569 key->ToString(context->GetIsolate());
570 // Skip all callbacks: crbug.com/139933
571 if (v8_object->HasRealNamedCallbackProperty(key_string))
572 continue;
574 v8::String::Utf8Value name_utf8(key_string);
576 v8::TryCatch try_catch;
577 v8::Handle<v8::Value> child_v8 = v8_object->Get(key);
578 if (try_catch.HasCaught())
579 return false;
581 PP_Var child_var;
582 if (!GetOrCreateVar(child_v8,
583 context,
584 instance_,
585 object_vars_allowed_,
586 &child_var,
587 &did_create,
588 &visited_handles,
589 &parent_handles,
590 resource_converter_.get())) {
591 return false;
593 if (did_create && child_v8->IsObject())
594 stack.push(child_v8);
596 bool success = dict_var->SetWithStringKey(
597 std::string(*name_utf8, name_utf8.length()), child_var);
598 DCHECK(success);
602 *result_var = root;
603 return true;
606 } // namespace content