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"
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
;
40 StackEntry(T v
) : val(v
), sentinel(false) {}
42 // Used to track parent nodes on the stack while traversing the graph.
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
;
56 namespace BASE_HASH_NAMESPACE
{
57 #if defined(COMPILER_GCC)
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(); }
65 } // namespace BASE_HASH_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
,
86 bool object_vars_allowed
,
87 v8::Handle
<v8::Value
>* result
,
89 VarHandleMap
* visited_ids
,
90 ParentVarSet
* parent_ids
,
91 ResourceConverter
* resource_converter
) {
92 v8::Isolate
* isolate
= context
->GetIsolate();
95 if (ppapi::VarTracker::IsVarTypeRefcounted(var
.type
)) {
96 if (parent_ids
->count(var
.value
.as_id
) != 0)
98 VarHandleMap::iterator it
= visited_ids
->find(var
.value
.as_id
);
99 if (it
!= visited_ids
->end()) {
100 *result
= it
->second
;
106 case PP_VARTYPE_UNDEFINED
:
107 *result
= v8::Undefined(isolate
);
109 case PP_VARTYPE_NULL
:
110 *result
= v8::Null(isolate
);
112 case PP_VARTYPE_BOOL
:
113 *result
= (var
.value
.as_bool
== PP_TRUE
) ? v8::True(isolate
)
114 : v8::False(isolate
);
116 case PP_VARTYPE_INT32
:
117 *result
= v8::Integer::New(isolate
, var
.value
.as_int
);
119 case PP_VARTYPE_DOUBLE
:
120 *result
= v8::Number::New(isolate
, var
.value
.as_double
);
122 case PP_VARTYPE_STRING
: {
123 StringVar
* string
= StringVar::FromPPVar(var
);
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());
138 case PP_VARTYPE_ARRAY_BUFFER
: {
139 ArrayBufferVar
* buffer
= ArrayBufferVar::FromPPVar(var
);
145 HostArrayBufferVar
* host_buffer
=
146 static_cast<HostArrayBufferVar
*>(buffer
);
147 *result
= blink::WebArrayBufferConverter::toV8Value(
148 &host_buffer
->webkit_buffer(), context
->Global(), isolate
);
151 case PP_VARTYPE_ARRAY
:
152 *result
= v8::Array::New(isolate
);
154 case PP_VARTYPE_DICTIONARY
:
155 *result
= v8::Object::New(isolate
);
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()) {
165 *result
= v8_object_var
->GetHandle();
168 case PP_VARTYPE_RESOURCE
:
169 if (!resource_converter
->ToV8Value(var
, context
, result
)) {
177 if (ppapi::VarTracker::IsVarTypeRefcounted(var
.type
))
178 (*visited_ids
)[var
.value
.as_id
] = *result
;
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
,
193 HandleVarMap
* visited_handles
,
194 ParentHandleSet
* parent_handles
,
195 ResourceConverter
* resource_converter
) {
196 CHECK(!val
.IsEmpty());
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)
206 HandleVarMap::const_iterator it
=
207 visited_handles
->find(HashedHandle(val
->ToObject()));
208 if (it
!= visited_handles
->end()) {
209 *result
= it
->second
.get();
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
);
243 if (!resource_converter
->FromV8Value(
244 val
->ToObject(), context
, result
, &was_resource
))
247 *result
= (new DictionaryVar())->GetPPVar();
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
258 if (val
->IsObject() || val
->IsString()) {
259 visited_handles
->insert(
260 make_pair(HashedHandle(val
->ToObject()),
261 ScopedPPVar(ScopedPPVar::PassRef(), *result
)));
266 bool CanHaveChildren(PP_Var var
) {
267 return var
.type
== PP_VARTYPE_ARRAY
|| var
.type
== PP_VARTYPE_DICTIONARY
;
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.
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
;
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
) {
328 if (CanHaveChildren(current_var
))
329 parent_ids
.erase(current_var
.value
.as_id
);
332 stack
.top().sentinel
= true;
335 bool did_create
= false;
336 if (!GetOrCreateV8Value(context
,
338 object_vars_allowed_
,
343 resource_converter_
.get())) {
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
);
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
,
368 object_vars_allowed_
,
373 resource_converter_
.get())) {
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.";
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
);
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();
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
,
404 object_vars_allowed_
,
409 resource_converter_
.get())) {
412 if (did_create
&& CanHaveChildren(child_var
))
413 stack
.push(child_var
);
414 v8::TryCatch try_catch
;
416 v8::String::NewFromUtf8(
417 isolate
, key
.c_str(), v8::String::kNormalString
, key
.length()),
419 if (try_catch
.HasCaught()) {
420 LOG(ERROR
) << "Setter for property " << key
.c_str() << " threw an "
428 *result
= handle_scope
.Escape(root
);
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
) {
437 result
.success
= FromV8ValueInternal(val
, context
, &result
.var
);
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
));
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();
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
));
472 *result_var
= PP_MakeUndefined();
475 while (!stack
.empty()) {
476 v8::Handle
<v8::Value
> current_v8
= stack
.top().val
;
479 if (stack
.top().sentinel
) {
481 if (current_v8
->IsObject())
482 parent_handles
.erase(HashedHandle(current_v8
->ToObject()));
485 stack
.top().sentinel
= true;
488 bool did_create
= false;
489 if (!GetOrCreateVar(current_v8
,
492 object_vars_allowed_
,
497 resource_converter_
.get())) {
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
);
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())
524 if (!v8_array
->HasRealIndexedProperty(i
))
528 if (!GetOrCreateVar(child_v8
,
531 object_vars_allowed_
,
536 resource_converter_
.get())) {
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
);
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
)
563 "is neither a string nor a number";
567 // Skip all callbacks: crbug.com/139933
568 if (v8_object
->HasRealNamedCallbackProperty(key
->ToString()))
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())
579 if (!GetOrCreateVar(child_v8
,
582 object_vars_allowed_
,
587 resource_converter_
.get())) {
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
);
603 } // namespace content