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