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.
4
5 #include "content/renderer/pepper/v8_var_converter.h"
6
7 #include <map>
8 #include <stack>
9 #include <string>
10
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
25 using ppapi::ArrayBufferVar;
26 using ppapi::ArrayVar;
27 using ppapi::DictionaryVar;
28 using ppapi::ScopedPPVar;
29 using ppapi::StringVar;
30 using std::make_pair;
31
32 namespace {
33
34 template <class T>
35 struct StackEntry {
StackEntry__anon457c74610111::StackEntry36 StackEntry(T v) : val(v), sentinel(false) {}
37 T val;
38 // Used to track parent nodes on the stack while traversing the graph.
39 bool sentinel;
40 };
41
42 struct HashedHandle {
HashedHandle__anon457c74610111::HashedHandle43 HashedHandle(v8::Handle<v8::Object> h) : handle(h) {}
hash__anon457c74610111::HashedHandle44 size_t hash() const { return handle->GetIdentityHash(); }
operator ==__anon457c74610111::HashedHandle45 bool operator==(const HashedHandle& h) const { return handle == h.handle; }
operator <__anon457c74610111::HashedHandle46 bool operator<(const HashedHandle& h) const { return hash() < h.hash(); }
47 v8::Handle<v8::Object> handle;
48 };
49
50 } // namespace
51
52 namespace BASE_HASH_NAMESPACE {
53 #if defined(COMPILER_GCC)
54 template <>
55 struct hash<HashedHandle> {
operator ()BASE_HASH_NAMESPACE::hash56 size_t operator()(const HashedHandle& handle) const {
57 return handle.hash();
58 }
59 };
60 #elif defined(COMPILER_MSVC)
61 inline size_t hash_value(const HashedHandle& handle) {
62 return handle.hash();
63 }
64 #endif
65 } // namespace BASE_HASH_NAMESPACE
66
67 namespace content {
68
69 namespace {
70
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;
74
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;
78
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.
GetOrCreateV8Value(v8::Isolate * isolate,const PP_Var & var,v8::Handle<v8::Value> * result,bool * did_create,VarHandleMap * visited_ids,ParentVarSet * parent_ids)84 bool GetOrCreateV8Value(v8::Isolate* isolate,
85 const PP_Var& var,
86 v8::Handle<v8::Value>* result,
87 bool* did_create,
88 VarHandleMap* visited_ids,
89 ParentVarSet* parent_ids) {
90 *did_create = false;
91
92 if (ppapi::VarTracker::IsVarTypeRefcounted(var.type)) {
93 if (parent_ids->count(var.value.as_id) != 0)
94 return false;
95 VarHandleMap::iterator it = visited_ids->find(var.value.as_id);
96 if (it != visited_ids->end()) {
97 *result = it->second;
98 return true;
99 }
100 }
101
102 switch (var.type) {
103 case PP_VARTYPE_UNDEFINED:
104 *result = v8::Undefined(isolate);
105 break;
106 case PP_VARTYPE_NULL:
107 *result = v8::Null(isolate);
108 break;
109 case PP_VARTYPE_BOOL:
110 *result = (var.value.as_bool == PP_TRUE)
111 ? v8::True(isolate)
112 : v8::False(isolate);
113 break;
114 case PP_VARTYPE_INT32:
115 *result = v8::Integer::New(var.value.as_int);
116 break;
117 case PP_VARTYPE_DOUBLE:
118 *result = v8::Number::New(var.value.as_double);
119 break;
120 case PP_VARTYPE_STRING: {
121 StringVar* string = StringVar::FromPPVar(var);
122 if (!string) {
123 NOTREACHED();
124 result->Clear();
125 return false;
126 }
127 const std::string& value = string->value();
128 // Create a string object rather than a string primitive. This allows us
129 // to have multiple references to the same string in javascript, which
130 // matches the reference behavior of PP_Vars.
131 *result = v8::String::NewFromUtf8(isolate,
132 value.c_str(),
133 v8::String::kNormalString,
134 value.size())->ToObject();
135 break;
136 }
137 case PP_VARTYPE_ARRAY_BUFFER: {
138 ArrayBufferVar* buffer = ArrayBufferVar::FromPPVar(var);
139 if (!buffer) {
140 NOTREACHED();
141 result->Clear();
142 return false;
143 }
144 HostArrayBufferVar* host_buffer =
145 static_cast<HostArrayBufferVar*>(buffer);
146 *result = host_buffer->webkit_buffer().toV8Value();
147 break;
148 }
149 case PP_VARTYPE_ARRAY:
150 *result = v8::Array::New(isolate);
151 break;
152 case PP_VARTYPE_DICTIONARY:
153 *result = v8::Object::New();
154 break;
155 case PP_VARTYPE_OBJECT:
156 case PP_VARTYPE_RESOURCE:
157 // TODO(mgiuca): Convert PP_VARTYPE_RESOURCE vars into the correct V8
158 // type. (http://crbug.com/177017)
159 result->Clear();
160 return false;
161 }
162
163 *did_create = true;
164 if (ppapi::VarTracker::IsVarTypeRefcounted(var.type))
165 (*visited_ids)[var.value.as_id] = *result;
166 return true;
167 }
168
169 // For a given V8 value handle, this returns a PP_Var which corresponds to it.
170 // If the handle already exists in |visited_handles|, the PP_Var associated with
171 // it will be returned, otherwise a new V8 value will be created and added to
172 // the map. |did_create| indicates if a new PP_Var was created as a result of
173 // calling the function.
GetOrCreateVar(v8::Handle<v8::Value> val,v8::Handle<v8::Context> context,PP_Var * result,bool * did_create,HandleVarMap * visited_handles,ParentHandleSet * parent_handles,ResourceConverter * resource_converter)174 bool GetOrCreateVar(v8::Handle<v8::Value> val,
175 v8::Handle<v8::Context> context,
176 PP_Var* result,
177 bool* did_create,
178 HandleVarMap* visited_handles,
179 ParentHandleSet* parent_handles,
180 ResourceConverter* resource_converter) {
181 CHECK(!val.IsEmpty());
182 *did_create = false;
183
184 // Even though every v8 string primitive encountered will be a unique object,
185 // we still add them to |visited_handles| so that the corresponding string
186 // PP_Var created will be properly refcounted.
187 if (val->IsObject() || val->IsString()) {
188 if (parent_handles->count(HashedHandle(val->ToObject())) != 0)
189 return false;
190
191 HandleVarMap::const_iterator it = visited_handles->find(
192 HashedHandle(val->ToObject()));
193 if (it != visited_handles->end()) {
194 *result = it->second.get();
195 return true;
196 }
197 }
198
199 if (val->IsUndefined()) {
200 *result = PP_MakeUndefined();
201 } else if (val->IsNull()) {
202 *result = PP_MakeNull();
203 } else if (val->IsBoolean() || val->IsBooleanObject()) {
204 *result = PP_MakeBool(PP_FromBool(val->ToBoolean()->Value()));
205 } else if (val->IsInt32()) {
206 *result = PP_MakeInt32(val->ToInt32()->Value());
207 } else if (val->IsNumber() || val->IsNumberObject()) {
208 *result = PP_MakeDouble(val->ToNumber()->Value());
209 } else if (val->IsString() || val->IsStringObject()) {
210 v8::String::Utf8Value utf8(val->ToString());
211 *result = StringVar::StringToPPVar(std::string(*utf8, utf8.length()));
212 } else if (val->IsArray()) {
213 *result = (new ArrayVar())->GetPPVar();
214 } else if (val->IsObject()) {
215 scoped_ptr<blink::WebArrayBuffer> web_array_buffer(
216 blink::WebArrayBuffer::createFromV8Value(val));
217 if (web_array_buffer.get()) {
218 scoped_refptr<HostArrayBufferVar> buffer_var(new HostArrayBufferVar(
219 *web_array_buffer));
220 *result = buffer_var->GetPPVar();
221 } else {
222 bool was_resource;
223 if (!resource_converter->FromV8Value(val->ToObject(), context, result,
224 &was_resource))
225 return false;
226 if (!was_resource) {
227 *result = (new DictionaryVar())->GetPPVar();
228 }
229 }
230 } else {
231 // Silently ignore the case where we can't convert to a Var as we may
232 // be trying to convert a type that doesn't have a corresponding
233 // PP_Var type.
234 return true;
235 }
236
237 *did_create = true;
238 if (val->IsObject() || val->IsString()) {
239 visited_handles->insert(make_pair(
240 HashedHandle(val->ToObject()),
241 ScopedPPVar(ScopedPPVar::PassRef(), *result)));
242 }
243 return true;
244 }
245
CanHaveChildren(PP_Var var)246 bool CanHaveChildren(PP_Var var) {
247 return var.type == PP_VARTYPE_ARRAY || var.type == PP_VARTYPE_DICTIONARY;
248 }
249
250 } // namespace
251
V8VarConverter(PP_Instance instance)252 V8VarConverter::V8VarConverter(PP_Instance instance)
253 : message_loop_proxy_(base::MessageLoopProxy::current()) {
254 resource_converter_.reset(new ResourceConverterImpl(
255 instance, RendererPpapiHost::GetForPPInstance(instance)));
256 }
257
V8VarConverter(PP_Instance instance,scoped_ptr<ResourceConverter> resource_converter)258 V8VarConverter::V8VarConverter(
259 PP_Instance instance,
260 scoped_ptr<ResourceConverter> resource_converter)
261 : message_loop_proxy_(base::MessageLoopProxy::current()),
262 resource_converter_(resource_converter.release()) {
263 }
264
~V8VarConverter()265 V8VarConverter::~V8VarConverter() {
266 }
267
268 // To/FromV8Value use a stack-based DFS search to traverse V8/Var graph. Each
269 // iteration, the top node on the stack examined. If the node has not been
270 // visited yet (i.e. sentinel == false) then it is added to the list of parents
271 // which contains all of the nodes on the path from the start node to the
272 // current node. Each of the current nodes children are examined. If they appear
273 // in the list of parents it means we have a cycle and we return NULL.
274 // Otherwise, if they can have children, we add them to the stack. If the
275 // node at the top of the stack has already been visited, then we pop it off the
276 // stack and erase it from the list of parents.
277 // static
ToV8Value(const PP_Var & var,v8::Handle<v8::Context> context,v8::Handle<v8::Value> * result)278 bool V8VarConverter::ToV8Value(const PP_Var& var,
279 v8::Handle<v8::Context> context,
280 v8::Handle<v8::Value>* result) {
281 v8::Context::Scope context_scope(context);
282 v8::Isolate* isolate = context->GetIsolate();
283 v8::EscapableHandleScope handle_scope(isolate);
284
285 VarHandleMap visited_ids;
286 ParentVarSet parent_ids;
287
288 std::stack<StackEntry<PP_Var> > stack;
289 stack.push(StackEntry<PP_Var>(var));
290 v8::Local<v8::Value> root;
291 bool is_root = true;
292
293 while (!stack.empty()) {
294 const PP_Var& current_var = stack.top().val;
295 v8::Handle<v8::Value> current_v8;
296
297 if (stack.top().sentinel) {
298 stack.pop();
299 if (CanHaveChildren(current_var))
300 parent_ids.erase(current_var.value.as_id);
301 continue;
302 } else {
303 stack.top().sentinel = true;
304 }
305
306 bool did_create = false;
307 if (!GetOrCreateV8Value(isolate, current_var, ¤t_v8, &did_create,
308 &visited_ids, &parent_ids)) {
309 return false;
310 }
311
312 if (is_root) {
313 is_root = false;
314 root = current_v8;
315 }
316
317 // Add child nodes to the stack.
318 if (current_var.type == PP_VARTYPE_ARRAY) {
319 parent_ids.insert(current_var.value.as_id);
320 ArrayVar* array_var = ArrayVar::FromPPVar(current_var);
321 if (!array_var) {
322 NOTREACHED();
323 return false;
324 }
325 DCHECK(current_v8->IsArray());
326 v8::Handle<v8::Array> v8_array = current_v8.As<v8::Array>();
327
328 for (size_t i = 0; i < array_var->elements().size(); ++i) {
329 const PP_Var& child_var = array_var->elements()[i].get();
330 v8::Handle<v8::Value> child_v8;
331 if (!GetOrCreateV8Value(isolate, child_var, &child_v8, &did_create,
332 &visited_ids, &parent_ids)) {
333 return false;
334 }
335 if (did_create && CanHaveChildren(child_var))
336 stack.push(child_var);
337 v8::TryCatch try_catch;
338 v8_array->Set(static_cast<uint32>(i), child_v8);
339 if (try_catch.HasCaught()) {
340 LOG(ERROR) << "Setter for index " << i << " threw an exception.";
341 return false;
342 }
343 }
344 } else if (current_var.type == PP_VARTYPE_DICTIONARY) {
345 parent_ids.insert(current_var.value.as_id);
346 DictionaryVar* dict_var = DictionaryVar::FromPPVar(current_var);
347 if (!dict_var) {
348 NOTREACHED();
349 return false;
350 }
351 DCHECK(current_v8->IsObject());
352 v8::Handle<v8::Object> v8_object = current_v8->ToObject();
353
354 for (DictionaryVar::KeyValueMap::const_iterator iter =
355 dict_var->key_value_map().begin();
356 iter != dict_var->key_value_map().end();
357 ++iter) {
358 const std::string& key = iter->first;
359 const PP_Var& child_var = iter->second.get();
360 v8::Handle<v8::Value> child_v8;
361 if (!GetOrCreateV8Value(isolate, child_var, &child_v8, &did_create,
362 &visited_ids, &parent_ids)) {
363 return false;
364 }
365 if (did_create && CanHaveChildren(child_var))
366 stack.push(child_var);
367 v8::TryCatch try_catch;
368 v8_object->Set(v8::String::NewFromUtf8(isolate,
369 key.c_str(),
370 v8::String::kNormalString,
371 key.length()),
372 child_v8);
373 if (try_catch.HasCaught()) {
374 LOG(ERROR) << "Setter for property " << key.c_str() << " threw an "
375 << "exception.";
376 return false;
377 }
378 }
379 }
380 }
381
382 *result = handle_scope.Escape(root);
383 return true;
384 }
385
FromV8Value(v8::Handle<v8::Value> val,v8::Handle<v8::Context> context,const base::Callback<void (const ScopedPPVar &,bool)> & callback)386 void V8VarConverter::FromV8Value(
387 v8::Handle<v8::Value> val,
388 v8::Handle<v8::Context> context,
389 const base::Callback<void(const ScopedPPVar&, bool)>& callback) {
390 v8::Context::Scope context_scope(context);
391 v8::HandleScope handle_scope(context->GetIsolate());
392
393 HandleVarMap visited_handles;
394 ParentHandleSet parent_handles;
395
396 std::stack<StackEntry<v8::Handle<v8::Value> > > stack;
397 stack.push(StackEntry<v8::Handle<v8::Value> >(val));
398 ScopedPPVar root;
399 bool is_root = true;
400
401 while (!stack.empty()) {
402 v8::Handle<v8::Value> current_v8 = stack.top().val;
403 PP_Var current_var;
404
405 if (stack.top().sentinel) {
406 stack.pop();
407 if (current_v8->IsObject())
408 parent_handles.erase(HashedHandle(current_v8->ToObject()));
409 continue;
410 } else {
411 stack.top().sentinel = true;
412 }
413
414 bool did_create = false;
415 if (!GetOrCreateVar(current_v8, context, ¤t_var, &did_create,
416 &visited_handles, &parent_handles,
417 resource_converter_.get())) {
418 message_loop_proxy_->PostTask(FROM_HERE,
419 base::Bind(callback, ScopedPPVar(PP_MakeUndefined()), false));
420 return;
421 }
422
423 if (is_root) {
424 is_root = false;
425 root = current_var;
426 }
427
428 // Add child nodes to the stack.
429 if (current_var.type == PP_VARTYPE_ARRAY) {
430 DCHECK(current_v8->IsArray());
431 v8::Handle<v8::Array> v8_array = current_v8.As<v8::Array>();
432 parent_handles.insert(HashedHandle(v8_array));
433
434 ArrayVar* array_var = ArrayVar::FromPPVar(current_var);
435 if (!array_var) {
436 NOTREACHED();
437 message_loop_proxy_->PostTask(FROM_HERE,
438 base::Bind(callback, ScopedPPVar(PP_MakeUndefined()), false));
439 return;
440 }
441
442 for (uint32 i = 0; i < v8_array->Length(); ++i) {
443 v8::TryCatch try_catch;
444 v8::Handle<v8::Value> child_v8 = v8_array->Get(i);
445 if (try_catch.HasCaught()) {
446 message_loop_proxy_->PostTask(FROM_HERE,
447 base::Bind(callback, ScopedPPVar(PP_MakeUndefined()), false));
448 return;
449 }
450
451 if (!v8_array->HasRealIndexedProperty(i))
452 continue;
453
454 PP_Var child_var;
455 if (!GetOrCreateVar(child_v8, context, &child_var, &did_create,
456 &visited_handles, &parent_handles,
457 resource_converter_.get())) {
458 message_loop_proxy_->PostTask(FROM_HERE,
459 base::Bind(callback, ScopedPPVar(PP_MakeUndefined()), false));
460 return;
461 }
462 if (did_create && child_v8->IsObject())
463 stack.push(child_v8);
464
465 array_var->Set(i, child_var);
466 }
467 } else if (current_var.type == PP_VARTYPE_DICTIONARY) {
468 DCHECK(current_v8->IsObject());
469 v8::Handle<v8::Object> v8_object = current_v8->ToObject();
470 parent_handles.insert(HashedHandle(v8_object));
471
472 DictionaryVar* dict_var = DictionaryVar::FromPPVar(current_var);
473 if (!dict_var) {
474 NOTREACHED();
475 message_loop_proxy_->PostTask(FROM_HERE,
476 base::Bind(callback, ScopedPPVar(PP_MakeUndefined()), false));
477 return;
478 }
479
480 v8::Handle<v8::Array> property_names(v8_object->GetOwnPropertyNames());
481 for (uint32 i = 0; i < property_names->Length(); ++i) {
482 v8::Handle<v8::Value> key(property_names->Get(i));
483
484 // Extend this test to cover more types as necessary and if sensible.
485 if (!key->IsString() && !key->IsNumber()) {
486 NOTREACHED() << "Key \"" << *v8::String::Utf8Value(key) << "\" "
487 "is neither a string nor a number";
488 message_loop_proxy_->PostTask(FROM_HERE,
489 base::Bind(callback, ScopedPPVar(PP_MakeUndefined()), false));
490 return;
491 }
492
493 // Skip all callbacks: crbug.com/139933
494 if (v8_object->HasRealNamedCallbackProperty(key->ToString()))
495 continue;
496
497 v8::String::Utf8Value name_utf8(key->ToString());
498
499 v8::TryCatch try_catch;
500 v8::Handle<v8::Value> child_v8 = v8_object->Get(key);
501 if (try_catch.HasCaught()) {
502 message_loop_proxy_->PostTask(FROM_HERE,
503 base::Bind(callback, ScopedPPVar(PP_MakeUndefined()), false));
504 return;
505 }
506
507 PP_Var child_var;
508 if (!GetOrCreateVar(child_v8, context, &child_var, &did_create,
509 &visited_handles, &parent_handles,
510 resource_converter_.get())) {
511 message_loop_proxy_->PostTask(FROM_HERE,
512 base::Bind(callback, ScopedPPVar(PP_MakeUndefined()), false));
513 return;
514 }
515 if (did_create && child_v8->IsObject())
516 stack.push(child_v8);
517
518 bool success = dict_var->SetWithStringKey(
519 std::string(*name_utf8, name_utf8.length()), child_var);
520 DCHECK(success);
521 }
522 }
523 }
524 resource_converter_->Flush(base::Bind(callback, root));
525 }
526
527 } // namespace content
528