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"
24 #include "third_party/WebKit/public/web/WebArrayBufferConverter.h"
26 using ppapi::ArrayBufferVar;
27 using ppapi::ArrayVar;
28 using ppapi::DictionaryVar;
29 using ppapi::ScopedPPVar;
30 using ppapi::StringVar;
37 StackEntry(T v) : val(v), sentinel(false) {}
39 // Used to track parent nodes on the stack while traversing the graph.
44 HashedHandle(v8::Handle<v8::Object> h) : handle(h) {}
45 size_t hash() const { return handle->GetIdentityHash(); }
46 bool operator==(const HashedHandle& h) const { return handle == h.handle; }
47 bool operator<(const HashedHandle& h) const { return hash() < h.hash(); }
48 v8::Handle<v8::Object> handle;
53 namespace BASE_HASH_NAMESPACE {
54 #if defined(COMPILER_GCC)
56 struct hash<HashedHandle> {
57 size_t operator()(const HashedHandle& handle) const { return handle.hash(); }
59 #elif defined(COMPILER_MSVC)
60 inline size_t hash_value(const HashedHandle& handle) { return handle.hash(); }
62 } // namespace BASE_HASH_NAMESPACE
68 // Maps PP_Var IDs to the V8 value handle they correspond to.
69 typedef base::hash_map<int64_t, v8::Handle<v8::Value> > VarHandleMap;
70 typedef base::hash_set<int64_t> ParentVarSet;
72 // Maps V8 value handles to the PP_Var they correspond to.
73 typedef base::hash_map<HashedHandle, ScopedPPVar> HandleVarMap;
74 typedef base::hash_set<HashedHandle> ParentHandleSet;
76 // Returns a V8 value which corresponds to a given PP_Var. If |var| is a
77 // reference counted PP_Var type, and it exists in |visited_ids|, the V8 value
78 // associated with it in the map will be returned, otherwise a new V8 value will
79 // be created and added to the map. |did_create| indicates whether a new v8
80 // value was created as a result of calling the function.
81 bool GetOrCreateV8Value(v8::Handle<v8::Context> context,
83 v8::Handle<v8::Value>* result,
85 VarHandleMap* visited_ids,
86 ParentVarSet* parent_ids,
87 ResourceConverter* resource_converter) {
88 v8::Isolate* isolate = context->GetIsolate();
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(isolate);
105 case PP_VARTYPE_NULL:
106 *result = v8::Null(isolate);
108 case PP_VARTYPE_BOOL:
109 *result = (var.value.as_bool == PP_TRUE) ? v8::True(isolate)
110 : v8::False(isolate);
112 case PP_VARTYPE_INT32:
113 *result = v8::Integer::New(isolate, var.value.as_int);
115 case PP_VARTYPE_DOUBLE:
116 *result = v8::Number::New(isolate, var.value.as_double);
118 case PP_VARTYPE_STRING: {
119 StringVar* string = StringVar::FromPPVar(var);
125 const std::string& value = string->value();
126 // Create a string primitive rather than a string object. This is lossy
127 // in the sense that string primitives in JavaScript can't be referenced
128 // in the same way that string vars can in pepper. But that information
129 // isn't very useful and primitive strings are a more expected form in JS.
130 *result = v8::String::NewFromUtf8(
131 isolate, value.c_str(), v8::String::kNormalString, value.size());
134 case PP_VARTYPE_ARRAY_BUFFER: {
135 ArrayBufferVar* buffer = ArrayBufferVar::FromPPVar(var);
141 HostArrayBufferVar* host_buffer =
142 static_cast<HostArrayBufferVar*>(buffer);
143 *result = blink::WebArrayBufferConverter::toV8Value(
144 &host_buffer->webkit_buffer());
147 case PP_VARTYPE_ARRAY:
148 *result = v8::Array::New(isolate);
150 case PP_VARTYPE_DICTIONARY:
151 *result = v8::Object::New(isolate);
153 case PP_VARTYPE_OBJECT:
156 case PP_VARTYPE_RESOURCE:
157 if (!resource_converter->ToV8Value(var, context, result)) {
165 if (ppapi::VarTracker::IsVarTypeRefcounted(var.type))
166 (*visited_ids)[var.value.as_id] = *result;
170 // For a given V8 value handle, this returns a PP_Var which corresponds to it.
171 // If the handle already exists in |visited_handles|, the PP_Var associated with
172 // it will be returned, otherwise a new V8 value will be created and added to
173 // the map. |did_create| indicates if a new PP_Var was created as a result of
174 // calling the function.
175 bool GetOrCreateVar(v8::Handle<v8::Value> val,
176 v8::Handle<v8::Context> context,
179 HandleVarMap* visited_handles,
180 ParentHandleSet* parent_handles,
181 ResourceConverter* resource_converter) {
182 CHECK(!val.IsEmpty());
185 // Even though every v8 string primitive encountered will be a unique object,
186 // we still add them to |visited_handles| so that the corresponding string
187 // PP_Var created will be properly refcounted.
188 if (val->IsObject() || val->IsString()) {
189 if (parent_handles->count(HashedHandle(val->ToObject())) != 0)
192 HandleVarMap::const_iterator it =
193 visited_handles->find(HashedHandle(val->ToObject()));
194 if (it != visited_handles->end()) {
195 *result = it->second.get();
200 if (val->IsUndefined()) {
201 *result = PP_MakeUndefined();
202 } else if (val->IsNull()) {
203 *result = PP_MakeNull();
204 } else if (val->IsBoolean() || val->IsBooleanObject()) {
205 *result = PP_MakeBool(PP_FromBool(val->ToBoolean()->Value()));
206 } else if (val->IsInt32()) {
207 *result = PP_MakeInt32(val->ToInt32()->Value());
208 } else if (val->IsNumber() || val->IsNumberObject()) {
209 *result = PP_MakeDouble(val->ToNumber()->Value());
210 } else if (val->IsString() || val->IsStringObject()) {
211 v8::String::Utf8Value utf8(val->ToString());
212 *result = StringVar::StringToPPVar(std::string(*utf8, utf8.length()));
213 } else if (val->IsArray()) {
214 *result = (new ArrayVar())->GetPPVar();
215 } else if (val->IsObject()) {
216 scoped_ptr<blink::WebArrayBuffer> web_array_buffer(
217 blink::WebArrayBufferConverter::createFromV8Value(val));
218 if (web_array_buffer.get()) {
219 scoped_refptr<HostArrayBufferVar> buffer_var(
220 new HostArrayBufferVar(*web_array_buffer));
221 *result = buffer_var->GetPPVar();
224 if (!resource_converter->FromV8Value(
225 val->ToObject(), context, result, &was_resource))
228 *result = (new DictionaryVar())->GetPPVar();
232 // Silently ignore the case where we can't convert to a Var as we may
233 // be trying to convert a type that doesn't have a corresponding
239 if (val->IsObject() || val->IsString()) {
240 visited_handles->insert(
241 make_pair(HashedHandle(val->ToObject()),
242 ScopedPPVar(ScopedPPVar::PassRef(), *result)));
247 bool CanHaveChildren(PP_Var var) {
248 return var.type == PP_VARTYPE_ARRAY || var.type == PP_VARTYPE_DICTIONARY;
253 V8VarConverter::V8VarConverter(PP_Instance instance)
254 : message_loop_proxy_(base::MessageLoopProxy::current()) {
255 resource_converter_.reset(new ResourceConverterImpl(
256 instance, RendererPpapiHost::GetForPPInstance(instance)));
259 V8VarConverter::V8VarConverter(PP_Instance instance,
260 scoped_ptr<ResourceConverter> resource_converter)
261 : message_loop_proxy_(base::MessageLoopProxy::current()),
262 resource_converter_(resource_converter.release()) {}
264 V8VarConverter::~V8VarConverter() {}
266 // To/FromV8Value use a stack-based DFS search to traverse V8/Var graph. Each
267 // iteration, the top node on the stack examined. If the node has not been
268 // visited yet (i.e. sentinel == false) then it is added to the list of parents
269 // which contains all of the nodes on the path from the start node to the
270 // current node. Each of the current nodes children are examined. If they appear
271 // in the list of parents it means we have a cycle and we return NULL.
272 // Otherwise, if they can have children, we add them to the stack. If the
273 // node at the top of the stack has already been visited, then we pop it off the
274 // stack and erase it from the list of parents.
276 bool V8VarConverter::ToV8Value(const PP_Var& var,
277 v8::Handle<v8::Context> context,
278 v8::Handle<v8::Value>* result) {
279 v8::Context::Scope context_scope(context);
280 v8::Isolate* isolate = context->GetIsolate();
281 v8::EscapableHandleScope handle_scope(isolate);
283 VarHandleMap visited_ids;
284 ParentVarSet parent_ids;
286 std::stack<StackEntry<PP_Var> > stack;
287 stack.push(StackEntry<PP_Var>(var));
288 v8::Local<v8::Value> root;
291 while (!stack.empty()) {
292 const PP_Var& current_var = stack.top().val;
293 v8::Handle<v8::Value> current_v8;
295 if (stack.top().sentinel) {
297 if (CanHaveChildren(current_var))
298 parent_ids.erase(current_var.value.as_id);
301 stack.top().sentinel = true;
304 bool did_create = false;
305 if (!GetOrCreateV8Value(context,
311 resource_converter_.get())) {
320 // Add child nodes to the stack.
321 if (current_var.type == PP_VARTYPE_ARRAY) {
322 parent_ids.insert(current_var.value.as_id);
323 ArrayVar* array_var = ArrayVar::FromPPVar(current_var);
328 DCHECK(current_v8->IsArray());
329 v8::Handle<v8::Array> v8_array = current_v8.As<v8::Array>();
331 for (size_t i = 0; i < array_var->elements().size(); ++i) {
332 const PP_Var& child_var = array_var->elements()[i].get();
333 v8::Handle<v8::Value> child_v8;
334 if (!GetOrCreateV8Value(context,
340 resource_converter_.get())) {
343 if (did_create && CanHaveChildren(child_var))
344 stack.push(child_var);
345 v8::TryCatch try_catch;
346 v8_array->Set(static_cast<uint32>(i), child_v8);
347 if (try_catch.HasCaught()) {
348 LOG(ERROR) << "Setter for index " << i << " threw an exception.";
352 } else if (current_var.type == PP_VARTYPE_DICTIONARY) {
353 parent_ids.insert(current_var.value.as_id);
354 DictionaryVar* dict_var = DictionaryVar::FromPPVar(current_var);
359 DCHECK(current_v8->IsObject());
360 v8::Handle<v8::Object> v8_object = current_v8->ToObject();
362 for (DictionaryVar::KeyValueMap::const_iterator iter =
363 dict_var->key_value_map().begin();
364 iter != dict_var->key_value_map().end();
366 const std::string& key = iter->first;
367 const PP_Var& child_var = iter->second.get();
368 v8::Handle<v8::Value> child_v8;
369 if (!GetOrCreateV8Value(context,
375 resource_converter_.get())) {
378 if (did_create && CanHaveChildren(child_var))
379 stack.push(child_var);
380 v8::TryCatch try_catch;
382 v8::String::NewFromUtf8(
383 isolate, key.c_str(), v8::String::kNormalString, key.length()),
385 if (try_catch.HasCaught()) {
386 LOG(ERROR) << "Setter for property " << key.c_str() << " threw an "
394 *result = handle_scope.Escape(root);
398 void V8VarConverter::FromV8Value(
399 v8::Handle<v8::Value> val,
400 v8::Handle<v8::Context> context,
401 const base::Callback<void(const ScopedPPVar&, bool)>& callback) {
402 v8::Context::Scope context_scope(context);
403 v8::HandleScope handle_scope(context->GetIsolate());
405 HandleVarMap visited_handles;
406 ParentHandleSet parent_handles;
408 std::stack<StackEntry<v8::Handle<v8::Value> > > stack;
409 stack.push(StackEntry<v8::Handle<v8::Value> >(val));
413 while (!stack.empty()) {
414 v8::Handle<v8::Value> current_v8 = stack.top().val;
417 if (stack.top().sentinel) {
419 if (current_v8->IsObject())
420 parent_handles.erase(HashedHandle(current_v8->ToObject()));
423 stack.top().sentinel = true;
426 bool did_create = false;
427 if (!GetOrCreateVar(current_v8,
433 resource_converter_.get())) {
434 message_loop_proxy_->PostTask(
436 base::Bind(callback, ScopedPPVar(PP_MakeUndefined()), false));
445 // Add child nodes to the stack.
446 if (current_var.type == PP_VARTYPE_ARRAY) {
447 DCHECK(current_v8->IsArray());
448 v8::Handle<v8::Array> v8_array = current_v8.As<v8::Array>();
449 parent_handles.insert(HashedHandle(v8_array));
451 ArrayVar* array_var = ArrayVar::FromPPVar(current_var);
454 message_loop_proxy_->PostTask(
456 base::Bind(callback, ScopedPPVar(PP_MakeUndefined()), false));
460 for (uint32 i = 0; i < v8_array->Length(); ++i) {
461 v8::TryCatch try_catch;
462 v8::Handle<v8::Value> child_v8 = v8_array->Get(i);
463 if (try_catch.HasCaught()) {
464 message_loop_proxy_->PostTask(
466 base::Bind(callback, ScopedPPVar(PP_MakeUndefined()), false));
470 if (!v8_array->HasRealIndexedProperty(i))
474 if (!GetOrCreateVar(child_v8,
480 resource_converter_.get())) {
481 message_loop_proxy_->PostTask(
483 base::Bind(callback, ScopedPPVar(PP_MakeUndefined()), false));
486 if (did_create && child_v8->IsObject())
487 stack.push(child_v8);
489 array_var->Set(i, child_var);
491 } else if (current_var.type == PP_VARTYPE_DICTIONARY) {
492 DCHECK(current_v8->IsObject());
493 v8::Handle<v8::Object> v8_object = current_v8->ToObject();
494 parent_handles.insert(HashedHandle(v8_object));
496 DictionaryVar* dict_var = DictionaryVar::FromPPVar(current_var);
499 message_loop_proxy_->PostTask(
501 base::Bind(callback, ScopedPPVar(PP_MakeUndefined()), false));
505 v8::Handle<v8::Array> property_names(v8_object->GetOwnPropertyNames());
506 for (uint32 i = 0; i < property_names->Length(); ++i) {
507 v8::Handle<v8::Value> key(property_names->Get(i));
509 // Extend this test to cover more types as necessary and if sensible.
510 if (!key->IsString() && !key->IsNumber()) {
511 NOTREACHED() << "Key \"" << *v8::String::Utf8Value(key)
513 "is neither a string nor a number";
514 message_loop_proxy_->PostTask(
516 base::Bind(callback, ScopedPPVar(PP_MakeUndefined()), false));
520 // Skip all callbacks: crbug.com/139933
521 if (v8_object->HasRealNamedCallbackProperty(key->ToString()))
524 v8::String::Utf8Value name_utf8(key->ToString());
526 v8::TryCatch try_catch;
527 v8::Handle<v8::Value> child_v8 = v8_object->Get(key);
528 if (try_catch.HasCaught()) {
529 message_loop_proxy_->PostTask(
531 base::Bind(callback, ScopedPPVar(PP_MakeUndefined()), false));
536 if (!GetOrCreateVar(child_v8,
542 resource_converter_.get())) {
543 message_loop_proxy_->PostTask(
545 base::Bind(callback, ScopedPPVar(PP_MakeUndefined()), false));
548 if (did_create && child_v8->IsObject())
549 stack.push(child_v8);
551 bool success = dict_var->SetWithStringKey(
552 std::string(*name_utf8, name_utf8.length()), child_var);
557 resource_converter_->Flush(base::Bind(callback, root));
560 } // namespace content