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/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"
25 using ppapi::ArrayBufferVar
;
26 using ppapi::ArrayVar
;
27 using ppapi::DictionaryVar
;
28 using ppapi::ScopedPPVar
;
29 using ppapi::StringVar
;
36 StackEntry(T v
) : val(v
), sentinel(false) {}
38 // Used to track parent nodes on the stack while traversing the graph.
43 HashedHandle(v8::Handle
<v8::Object
> h
) : handle(h
) {}
44 size_t hash() const { return handle
->GetIdentityHash(); }
45 bool operator==(const HashedHandle
& h
) const { return handle
== h
.handle
; }
46 bool operator<(const HashedHandle
& h
) const { return hash() < h
.hash(); }
47 v8::Handle
<v8::Object
> handle
;
52 namespace BASE_HASH_NAMESPACE
{
53 #if defined(COMPILER_GCC)
55 struct hash
<HashedHandle
> {
56 size_t operator()(const HashedHandle
& handle
) const {
60 #elif defined(COMPILER_MSVC)
61 inline size_t hash_value(const HashedHandle
& handle
) {
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(const PP_Var
& var
,
85 v8::Handle
<v8::Value
>* result
,
87 VarHandleMap
* visited_ids
,
88 ParentVarSet
* parent_ids
) {
91 if (ppapi::VarTracker::IsVarTypeRefcounted(var
.type
)) {
92 if (parent_ids
->count(var
.value
.as_id
) != 0)
94 VarHandleMap::iterator it
= visited_ids
->find(var
.value
.as_id
);
95 if (it
!= visited_ids
->end()) {
102 case PP_VARTYPE_UNDEFINED
:
103 *result
= v8::Undefined();
105 case PP_VARTYPE_NULL
:
106 *result
= v8::Null();
108 case PP_VARTYPE_BOOL
:
109 *result
= (var
.value
.as_bool
== PP_TRUE
) ? v8::True() : v8::False();
111 case PP_VARTYPE_INT32
:
112 *result
= v8::Integer::New(var
.value
.as_int
);
114 case PP_VARTYPE_DOUBLE
:
115 *result
= v8::Number::New(var
.value
.as_double
);
117 case PP_VARTYPE_STRING
: {
118 StringVar
* string
= StringVar::FromPPVar(var
);
124 const std::string
& value
= string
->value();
125 // Create a string object rather than a string primitive. This allows us
126 // to have multiple references to the same string in javascript, which
127 // matches the reference behavior of PP_Vars.
128 *result
= v8::String::New(value
.c_str(), value
.size())->ToObject();
131 case PP_VARTYPE_ARRAY_BUFFER
: {
132 ArrayBufferVar
* buffer
= ArrayBufferVar::FromPPVar(var
);
138 HostArrayBufferVar
* host_buffer
=
139 static_cast<HostArrayBufferVar
*>(buffer
);
140 *result
= host_buffer
->webkit_buffer().toV8Value();
143 case PP_VARTYPE_ARRAY
:
144 *result
= v8::Array::New();
146 case PP_VARTYPE_DICTIONARY
:
147 *result
= v8::Object::New();
149 case PP_VARTYPE_OBJECT
:
150 case PP_VARTYPE_RESOURCE
:
151 // TODO(mgiuca): Convert PP_VARTYPE_RESOURCE vars into the correct V8
152 // type. (http://crbug.com/177017)
158 if (ppapi::VarTracker::IsVarTypeRefcounted(var
.type
))
159 (*visited_ids
)[var
.value
.as_id
] = *result
;
163 // For a given V8 value handle, this returns a PP_Var which corresponds to it.
164 // If the handle already exists in |visited_handles|, the PP_Var associated with
165 // it will be returned, otherwise a new V8 value will be created and added to
166 // the map. |did_create| indicates if a new PP_Var was created as a result of
167 // calling the function.
168 bool GetOrCreateVar(v8::Handle
<v8::Value
> val
,
169 v8::Handle
<v8::Context
> context
,
172 HandleVarMap
* visited_handles
,
173 ParentHandleSet
* parent_handles
,
174 ResourceConverter
* resource_converter
) {
175 CHECK(!val
.IsEmpty());
178 // Even though every v8 string primitive encountered will be a unique object,
179 // we still add them to |visited_handles| so that the corresponding string
180 // PP_Var created will be properly refcounted.
181 if (val
->IsObject() || val
->IsString()) {
182 if (parent_handles
->count(HashedHandle(val
->ToObject())) != 0)
185 HandleVarMap::const_iterator it
= visited_handles
->find(
186 HashedHandle(val
->ToObject()));
187 if (it
!= visited_handles
->end()) {
188 *result
= it
->second
.get();
193 if (val
->IsUndefined()) {
194 *result
= PP_MakeUndefined();
195 } else if (val
->IsNull()) {
196 *result
= PP_MakeNull();
197 } else if (val
->IsBoolean() || val
->IsBooleanObject()) {
198 *result
= PP_MakeBool(PP_FromBool(val
->ToBoolean()->Value()));
199 } else if (val
->IsInt32()) {
200 *result
= PP_MakeInt32(val
->ToInt32()->Value());
201 } else if (val
->IsNumber() || val
->IsNumberObject()) {
202 *result
= PP_MakeDouble(val
->ToNumber()->Value());
203 } else if (val
->IsString() || val
->IsStringObject()) {
204 v8::String::Utf8Value
utf8(val
->ToString());
205 *result
= StringVar::StringToPPVar(std::string(*utf8
, utf8
.length()));
206 } else if (val
->IsArray()) {
207 *result
= (new ArrayVar())->GetPPVar();
208 } else if (val
->IsObject()) {
209 scoped_ptr
<WebKit::WebArrayBuffer
> web_array_buffer(
210 WebKit::WebArrayBuffer::createFromV8Value(val
));
211 if (web_array_buffer
.get()) {
212 scoped_refptr
<HostArrayBufferVar
> buffer_var(new HostArrayBufferVar(
214 *result
= buffer_var
->GetPPVar();
217 if (!resource_converter
->FromV8Value(val
->ToObject(), context
, result
,
221 *result
= (new DictionaryVar())->GetPPVar();
225 // Silently ignore the case where we can't convert to a Var as we may
226 // be trying to convert a type that doesn't have a corresponding
232 if (val
->IsObject() || val
->IsString()) {
233 visited_handles
->insert(make_pair(
234 HashedHandle(val
->ToObject()),
235 ScopedPPVar(ScopedPPVar::PassRef(), *result
)));
240 bool CanHaveChildren(PP_Var var
) {
241 return var
.type
== PP_VARTYPE_ARRAY
|| var
.type
== PP_VARTYPE_DICTIONARY
;
246 V8VarConverter::V8VarConverter(PP_Instance instance
)
247 : message_loop_proxy_(base::MessageLoopProxy::current()) {
248 resource_converter_
.reset(new ResourceConverterImpl(
249 instance
, RendererPpapiHost::GetForPPInstance(instance
)));
252 V8VarConverter::V8VarConverter(
253 PP_Instance instance
,
254 scoped_ptr
<ResourceConverter
> resource_converter
)
255 : message_loop_proxy_(base::MessageLoopProxy::current()),
256 resource_converter_(resource_converter
.release()) {
259 V8VarConverter::~V8VarConverter() {
262 // To/FromV8Value use a stack-based DFS search to traverse V8/Var graph. Each
263 // iteration, the top node on the stack examined. If the node has not been
264 // visited yet (i.e. sentinel == false) then it is added to the list of parents
265 // which contains all of the nodes on the path from the start node to the
266 // current node. Each of the current nodes children are examined. If they appear
267 // in the list of parents it means we have a cycle and we return NULL.
268 // Otherwise, if they can have children, we add them to the stack. If the
269 // node at the top of the stack has already been visited, then we pop it off the
270 // stack and erase it from the list of parents.
272 bool V8VarConverter::ToV8Value(const PP_Var
& var
,
273 v8::Handle
<v8::Context
> context
,
274 v8::Handle
<v8::Value
>* result
) {
275 v8::Context::Scope
context_scope(context
);
276 v8::HandleScope
handle_scope(context
->GetIsolate());
278 VarHandleMap visited_ids
;
279 ParentVarSet parent_ids
;
281 std::stack
<StackEntry
<PP_Var
> > stack
;
282 stack
.push(StackEntry
<PP_Var
>(var
));
283 v8::Handle
<v8::Value
> root
;
286 while (!stack
.empty()) {
287 const PP_Var
& current_var
= stack
.top().val
;
288 v8::Handle
<v8::Value
> current_v8
;
290 if (stack
.top().sentinel
) {
292 if (CanHaveChildren(current_var
))
293 parent_ids
.erase(current_var
.value
.as_id
);
296 stack
.top().sentinel
= true;
299 bool did_create
= false;
300 if (!GetOrCreateV8Value(current_var
, ¤t_v8
, &did_create
,
301 &visited_ids
, &parent_ids
)) {
310 // Add child nodes to the stack.
311 if (current_var
.type
== PP_VARTYPE_ARRAY
) {
312 parent_ids
.insert(current_var
.value
.as_id
);
313 ArrayVar
* array_var
= ArrayVar::FromPPVar(current_var
);
318 DCHECK(current_v8
->IsArray());
319 v8::Handle
<v8::Array
> v8_array
= current_v8
.As
<v8::Array
>();
321 for (size_t i
= 0; i
< array_var
->elements().size(); ++i
) {
322 const PP_Var
& child_var
= array_var
->elements()[i
].get();
323 v8::Handle
<v8::Value
> child_v8
;
324 if (!GetOrCreateV8Value(child_var
, &child_v8
, &did_create
,
325 &visited_ids
, &parent_ids
)) {
328 if (did_create
&& CanHaveChildren(child_var
))
329 stack
.push(child_var
);
330 v8::TryCatch try_catch
;
331 v8_array
->Set(static_cast<uint32
>(i
), child_v8
);
332 if (try_catch
.HasCaught()) {
333 LOG(ERROR
) << "Setter for index " << i
<< " threw an exception.";
337 } else if (current_var
.type
== PP_VARTYPE_DICTIONARY
) {
338 parent_ids
.insert(current_var
.value
.as_id
);
339 DictionaryVar
* dict_var
= DictionaryVar::FromPPVar(current_var
);
344 DCHECK(current_v8
->IsObject());
345 v8::Handle
<v8::Object
> v8_object
= current_v8
->ToObject();
347 for (DictionaryVar::KeyValueMap::const_iterator iter
=
348 dict_var
->key_value_map().begin();
349 iter
!= dict_var
->key_value_map().end();
351 const std::string
& key
= iter
->first
;
352 const PP_Var
& child_var
= iter
->second
.get();
353 v8::Handle
<v8::Value
> child_v8
;
354 if (!GetOrCreateV8Value(child_var
, &child_v8
, &did_create
,
355 &visited_ids
, &parent_ids
)) {
358 if (did_create
&& CanHaveChildren(child_var
))
359 stack
.push(child_var
);
360 v8::TryCatch try_catch
;
361 v8_object
->Set(v8::String::New(key
.c_str(), key
.length()), child_v8
);
362 if (try_catch
.HasCaught()) {
363 LOG(ERROR
) << "Setter for property " << key
.c_str() << " threw an "
371 *result
= handle_scope
.Close(root
);
375 void V8VarConverter::FromV8Value(
376 v8::Handle
<v8::Value
> val
,
377 v8::Handle
<v8::Context
> context
,
378 const base::Callback
<void(const ScopedPPVar
&, bool)>& callback
) {
379 v8::Context::Scope
context_scope(context
);
380 v8::HandleScope
handle_scope(context
->GetIsolate());
382 HandleVarMap visited_handles
;
383 ParentHandleSet parent_handles
;
385 std::stack
<StackEntry
<v8::Handle
<v8::Value
> > > stack
;
386 stack
.push(StackEntry
<v8::Handle
<v8::Value
> >(val
));
390 while (!stack
.empty()) {
391 v8::Handle
<v8::Value
> current_v8
= stack
.top().val
;
394 if (stack
.top().sentinel
) {
396 if (current_v8
->IsObject())
397 parent_handles
.erase(HashedHandle(current_v8
->ToObject()));
400 stack
.top().sentinel
= true;
403 bool did_create
= false;
404 if (!GetOrCreateVar(current_v8
, context
, ¤t_var
, &did_create
,
405 &visited_handles
, &parent_handles
,
406 resource_converter_
.get())) {
407 message_loop_proxy_
->PostTask(FROM_HERE
,
408 base::Bind(callback
, ScopedPPVar(PP_MakeUndefined()), false));
417 // Add child nodes to the stack.
418 if (current_var
.type
== PP_VARTYPE_ARRAY
) {
419 DCHECK(current_v8
->IsArray());
420 v8::Handle
<v8::Array
> v8_array
= current_v8
.As
<v8::Array
>();
421 parent_handles
.insert(HashedHandle(v8_array
));
423 ArrayVar
* array_var
= ArrayVar::FromPPVar(current_var
);
426 message_loop_proxy_
->PostTask(FROM_HERE
,
427 base::Bind(callback
, ScopedPPVar(PP_MakeUndefined()), false));
431 for (uint32 i
= 0; i
< v8_array
->Length(); ++i
) {
432 v8::TryCatch try_catch
;
433 v8::Handle
<v8::Value
> child_v8
= v8_array
->Get(i
);
434 if (try_catch
.HasCaught()) {
435 message_loop_proxy_
->PostTask(FROM_HERE
,
436 base::Bind(callback
, ScopedPPVar(PP_MakeUndefined()), false));
440 if (!v8_array
->HasRealIndexedProperty(i
))
444 if (!GetOrCreateVar(child_v8
, context
, &child_var
, &did_create
,
445 &visited_handles
, &parent_handles
,
446 resource_converter_
.get())) {
447 message_loop_proxy_
->PostTask(FROM_HERE
,
448 base::Bind(callback
, ScopedPPVar(PP_MakeUndefined()), false));
451 if (did_create
&& child_v8
->IsObject())
452 stack
.push(child_v8
);
454 array_var
->Set(i
, child_var
);
456 } else if (current_var
.type
== PP_VARTYPE_DICTIONARY
) {
457 DCHECK(current_v8
->IsObject());
458 v8::Handle
<v8::Object
> v8_object
= current_v8
->ToObject();
459 parent_handles
.insert(HashedHandle(v8_object
));
461 DictionaryVar
* dict_var
= DictionaryVar::FromPPVar(current_var
);
464 message_loop_proxy_
->PostTask(FROM_HERE
,
465 base::Bind(callback
, ScopedPPVar(PP_MakeUndefined()), false));
469 v8::Handle
<v8::Array
> property_names(v8_object
->GetOwnPropertyNames());
470 for (uint32 i
= 0; i
< property_names
->Length(); ++i
) {
471 v8::Handle
<v8::Value
> key(property_names
->Get(i
));
473 // Extend this test to cover more types as necessary and if sensible.
474 if (!key
->IsString() && !key
->IsNumber()) {
475 NOTREACHED() << "Key \"" << *v8::String::AsciiValue(key
) << "\" "
476 "is neither a string nor a number";
477 message_loop_proxy_
->PostTask(FROM_HERE
,
478 base::Bind(callback
, ScopedPPVar(PP_MakeUndefined()), false));
482 // Skip all callbacks: crbug.com/139933
483 if (v8_object
->HasRealNamedCallbackProperty(key
->ToString()))
486 v8::String::Utf8Value
name_utf8(key
->ToString());
488 v8::TryCatch try_catch
;
489 v8::Handle
<v8::Value
> child_v8
= v8_object
->Get(key
);
490 if (try_catch
.HasCaught()) {
491 message_loop_proxy_
->PostTask(FROM_HERE
,
492 base::Bind(callback
, ScopedPPVar(PP_MakeUndefined()), false));
497 if (!GetOrCreateVar(child_v8
, context
, &child_var
, &did_create
,
498 &visited_handles
, &parent_handles
,
499 resource_converter_
.get())) {
500 message_loop_proxy_
->PostTask(FROM_HERE
,
501 base::Bind(callback
, ScopedPPVar(PP_MakeUndefined()), false));
504 if (did_create
&& child_v8
->IsObject())
505 stack
.push(child_v8
);
507 bool success
= dict_var
->SetWithStringKey(
508 std::string(*name_utf8
, name_utf8
.length()), child_var
);
513 resource_converter_
->Flush(base::Bind(callback
, root
));
516 } // namespace content