diff options
author | raymes@chromium.org <raymes@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-05-16 18:03:57 +0000 |
---|---|---|
committer | raymes@chromium.org <raymes@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-05-16 18:03:57 +0000 |
commit | 7e3461079b02605faf2a1e767bfad03bb5e8eaf1 (patch) | |
tree | 951e1edba29f6461f74cfb6564383e6183148d3f | |
parent | 05893fabc7515cb9fc253ef5a9822d3325e6d344 (diff) | |
download | chromium_src-7e3461079b02605faf2a1e767bfad03bb5e8eaf1.zip chromium_src-7e3461079b02605faf2a1e767bfad03bb5e8eaf1.tar.gz chromium_src-7e3461079b02605faf2a1e767bfad03bb5e8eaf1.tar.bz2 |
Implement a V8 value<->PP_Var converter
NOTE: This patch is being relanded after it was reverted in 199944 due to a unittest breakage, fixed by 200279.
This implements a converter for transforming between V8 values and PP_Vars. This is needed to support transferring arrays/dictionaries (or arrays/objects in javascript) to and from the plugin using the pepper Post/HandleMessage APIs. The entire object graph is converted in the process.
TBR=dmichael@chromium.org
BUG=236958
Committed: https://src.chromium.org/viewvc/chrome?view=rev&revision=199938
Committed: https://src.chromium.org/viewvc/chrome?view=rev&revision=200283
Review URL: https://codereview.chromium.org/14424006
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@200578 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | content/content_tests.gypi | 1 | ||||
-rw-r--r-- | ppapi/ppapi_tests.gypi | 2 | ||||
-rw-r--r-- | ppapi/proxy/raw_var_data_unittest.cc | 156 | ||||
-rw-r--r-- | ppapi/shared_impl/unittest_utils.cc | 166 | ||||
-rw-r--r-- | ppapi/shared_impl/unittest_utils.h | 19 | ||||
-rw-r--r-- | webkit/plugins/ppapi/v8_var_converter.cc | 423 | ||||
-rw-r--r-- | webkit/plugins/ppapi/v8_var_converter.h | 41 | ||||
-rw-r--r-- | webkit/plugins/ppapi/v8_var_converter_unittest.cc | 352 | ||||
-rw-r--r-- | webkit/plugins/webkit_plugins.gypi | 2 |
9 files changed, 1010 insertions, 152 deletions
diff --git a/content/content_tests.gypi b/content/content_tests.gypi index fc57eff..b363ffd 100644 --- a/content/content_tests.gypi +++ b/content/content_tests.gypi @@ -528,6 +528,7 @@ '../webkit/plugins/ppapi/ppapi_unittest.cc', '../webkit/plugins/ppapi/ppapi_unittest.h', '../webkit/plugins/ppapi/quota_file_io_unittest.cc', + '../webkit/plugins/ppapi/v8_var_converter_unittest.cc', '../webkit/quota/mock_quota_manager.cc', '../webkit/quota/mock_quota_manager.h', '../webkit/quota/mock_quota_manager_unittest.cc', diff --git a/ppapi/ppapi_tests.gypi b/ppapi/ppapi_tests.gypi index ffd577f..3e5dbf1 100644 --- a/ppapi/ppapi_tests.gypi +++ b/ppapi/ppapi_tests.gypi @@ -101,6 +101,8 @@ 'proxy/resource_message_test_sink.h', 'shared_impl/test_globals.cc', 'shared_impl/test_globals.h', + 'shared_impl/unittest_utils.cc', + 'shared_impl/unittest_utils.h', ], }, diff --git a/ppapi/proxy/raw_var_data_unittest.cc b/ppapi/proxy/raw_var_data_unittest.cc index 8a00d21..2b134af 100644 --- a/ppapi/proxy/raw_var_data_unittest.cc +++ b/ppapi/proxy/raw_var_data_unittest.cc @@ -4,8 +4,6 @@ #include "ppapi/proxy/raw_var_data.h" -#include <cmath> - #include "base/logging.h" #include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" @@ -18,6 +16,7 @@ #include "ppapi/shared_impl/proxy_lock.h" #include "ppapi/shared_impl/scoped_pp_var.h" #include "ppapi/shared_impl/test_globals.h" +#include "ppapi/shared_impl/unittest_utils.h" #include "ppapi/shared_impl/var.h" #include "ppapi/shared_impl/var_tracker.h" #include "testing/gtest/include/gtest/gtest.h" @@ -45,153 +44,6 @@ class RawVarDataTest : public testing::Test { TestGlobals globals_; }; -// Compares two vars for equality. When two vars are found to be equal, an entry -// is inserted into |visited_map| with (expected id, actual id). When comparing -// two PP_Vars that have a graph of references, this avoids following reference -// cycles. It also ensures that a var with ID x in the graph is always equal -// to a var with ID y. This guarantees that the topology of the two graphs -// being compared is identical. -bool Equals(const PP_Var& expected, - const PP_Var& actual, - base::hash_map<int64_t, int64_t>* visited_map) { - if (expected.type != actual.type) { - LOG(ERROR) << "expected type: " << expected.type << - " actual type: " << actual.type; - return false; - } - if (VarTracker::IsVarTypeRefcounted(expected.type)) { - base::hash_map<int64_t, int64_t>::iterator it = - visited_map->find(expected.value.as_id); - if (it != visited_map->end()) { - if (it->second != actual.value.as_id) { - LOG(ERROR) << "expected id: " << it->second << " actual id: " << - actual.value.as_id; - return false; - } else { - return true; - } - } else { - (*visited_map)[expected.value.as_id] = actual.value.as_id; - } - } - switch (expected.type) { - case PP_VARTYPE_UNDEFINED: - return true; - case PP_VARTYPE_NULL: - return true; - case PP_VARTYPE_BOOL: - if (expected.value.as_bool != actual.value.as_bool) { - LOG(ERROR) << "expected: " << expected.value.as_bool << " actual: " << - actual.value.as_bool; - return false; - } - return true; - case PP_VARTYPE_INT32: - if (expected.value.as_int != actual.value.as_int) { - LOG(ERROR) << "expected: " << expected.value.as_int << " actual: " << - actual.value.as_int; - return false; - } - return true; - case PP_VARTYPE_DOUBLE: - if (fabs(expected.value.as_double - actual.value.as_double) > 1.0e-4) { - LOG(ERROR) << "expected: " << expected.value.as_double << - " actual: " << actual.value.as_double; - return false; - } - return true; - case PP_VARTYPE_OBJECT: - if (expected.value.as_id != actual.value.as_id) { - LOG(ERROR) << "expected: " << expected.value.as_id << " actual: " << - actual.value.as_id; - return false; - } - return true; - case PP_VARTYPE_STRING: { - StringVar* expected_var = StringVar::FromPPVar(expected); - StringVar* actual_var = StringVar::FromPPVar(actual); - DCHECK(expected_var && actual_var); - if (expected_var->value() != actual_var->value()) { - LOG(ERROR) << "expected: " << expected_var->value() << " actual: " << - actual_var->value(); - return false; - } - return true; - } - case PP_VARTYPE_ARRAY_BUFFER: { - ArrayBufferVar* expected_var = ArrayBufferVar::FromPPVar(expected); - ArrayBufferVar* actual_var = ArrayBufferVar::FromPPVar(actual); - DCHECK(expected_var && actual_var); - if (expected_var->ByteLength() != actual_var->ByteLength()) { - LOG(ERROR) << "expected: " << expected_var->ByteLength() << - " actual: " << actual_var->ByteLength(); - return false; - } - if (memcmp(expected_var->Map(), actual_var->Map(), - expected_var->ByteLength()) != 0) { - LOG(ERROR) << "expected array buffer does not match actual."; - return false; - } - return true; - } - case PP_VARTYPE_ARRAY: { - ArrayVar* expected_var = ArrayVar::FromPPVar(expected); - ArrayVar* actual_var = ArrayVar::FromPPVar(actual); - DCHECK(expected_var && actual_var); - if (expected_var->elements().size() != actual_var->elements().size()) { - LOG(ERROR) << "expected: " << expected_var->elements().size() << - " actual: " << actual_var->elements().size(); - return false; - } - for (size_t i = 0; i < expected_var->elements().size(); ++i) { - if (!Equals(expected_var->elements()[i].get(), - actual_var->elements()[i].get(), - visited_map)) { - return false; - } - } - return true; - } - case PP_VARTYPE_DICTIONARY: { - DictionaryVar* expected_var = DictionaryVar::FromPPVar(expected); - DictionaryVar* actual_var = DictionaryVar::FromPPVar(actual); - DCHECK(expected_var && actual_var); - if (expected_var->key_value_map().size() != - actual_var->key_value_map().size()) { - LOG(ERROR) << "expected: " << expected_var->key_value_map().size() << - " actual: " << actual_var->key_value_map().size(); - return false; - } - DictionaryVar::KeyValueMap::const_iterator expected_iter = - expected_var->key_value_map().begin(); - DictionaryVar::KeyValueMap::const_iterator actual_iter = - actual_var->key_value_map().begin(); - for ( ; expected_iter != expected_var->key_value_map().end(); - ++expected_iter, ++actual_iter) { - if (expected_iter->first != actual_iter->first) { - LOG(ERROR) << "expected: " << expected_iter->first << - " actual: " << actual_iter->first; - return false; - } - if (!Equals(expected_iter->second.get(), - actual_iter->second.get(), - visited_map)) { - return false; - } - } - return true; - } - } - NOTREACHED(); - return false; -} - -bool Equals(const PP_Var& expected, - const PP_Var& actual) { - base::hash_map<int64_t, int64_t> visited_map; - return Equals(expected, actual, &visited_map); -} - PP_Var WriteAndRead(const PP_Var& var) { PP_Instance dummy_instance = 1234; scoped_ptr<RawVarDataGraph> expected_data(RawVarDataGraph::Create( @@ -207,7 +59,7 @@ PP_Var WriteAndRead(const PP_Var& var) { bool WriteReadAndCompare(const PP_Var& var) { ScopedPPVar expected(ScopedPPVar::PassRef(), var); ScopedPPVar actual(ScopedPPVar::PassRef(), WriteAndRead(expected.get())); - return Equals(expected.get(), actual.get()); + return TestEqual(expected.get(), actual.get()); } } // namespace @@ -310,7 +162,7 @@ TEST_F(RawVarDataTest, DictionaryArrayTest) { dictionary->SetWithStringKey("10", release_array.get()); ScopedPPVar result = ScopedPPVar(ScopedPPVar::PassRef(), WriteAndRead(release_dictionary.get())); - EXPECT_TRUE(Equals(release_dictionary.get(), result.get())); + EXPECT_TRUE(TestEqual(release_dictionary.get(), result.get())); // Break the cycle. // TODO(raymes): We need some better machinery for releasing vars with // cycles. Remove the code below once we have that. @@ -322,7 +174,7 @@ TEST_F(RawVarDataTest, DictionaryArrayTest) { array->Set(index, release_array.get()); result = ScopedPPVar(ScopedPPVar::PassRef(), WriteAndRead(release_array.get())); - EXPECT_TRUE(Equals(release_array.get(), result.get())); + EXPECT_TRUE(TestEqual(release_array.get(), result.get())); // Break the self reference. array->Set(index, PP_MakeUndefined()); ArrayVar* result_array = ArrayVar::FromPPVar(result.get()); diff --git a/ppapi/shared_impl/unittest_utils.cc b/ppapi/shared_impl/unittest_utils.cc new file mode 100644 index 0000000..2c32b30 --- /dev/null +++ b/ppapi/shared_impl/unittest_utils.cc @@ -0,0 +1,166 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ppapi/shared_impl/unittest_utils.h" + +#include <cmath> + +#include "base/hash_tables.h" +#include "base/logging.h" +#include "ppapi/shared_impl/array_var.h" +#include "ppapi/shared_impl/dictionary_var.h" +#include "ppapi/shared_impl/var.h" +#include "ppapi/shared_impl/var_tracker.h" + +namespace ppapi { + +namespace { + +// When two vars x and y are found to be equal, an entry is inserted into +// |visited_map| with (x.value.as_id, y.value.as_id). This allows reference +// cycles to be avoided. It also allows us to associate nodes in |expected| with +// nodes in |actual| and check whether the graphs have equivalent topology. +bool Equals(const PP_Var& expected, + const PP_Var& actual, + base::hash_map<int64_t, int64_t>* visited_map) { + if (expected.type != actual.type) { + LOG(ERROR) << "expected type: " << expected.type << + " actual type: " << actual.type; + return false; + } + if (VarTracker::IsVarTypeRefcounted(expected.type)) { + base::hash_map<int64_t, int64_t>::iterator it = + visited_map->find(expected.value.as_id); + if (it != visited_map->end()) { + if (it->second != actual.value.as_id) { + LOG(ERROR) << "expected id: " << it->second << " actual id: " << + actual.value.as_id; + return false; + } else { + return true; + } + } else { + (*visited_map)[expected.value.as_id] = actual.value.as_id; + } + } + switch (expected.type) { + case PP_VARTYPE_UNDEFINED: + return true; + case PP_VARTYPE_NULL: + return true; + case PP_VARTYPE_BOOL: + if (expected.value.as_bool != actual.value.as_bool) { + LOG(ERROR) << "expected: " << expected.value.as_bool << " actual: " << + actual.value.as_bool; + return false; + } + return true; + case PP_VARTYPE_INT32: + if (expected.value.as_int != actual.value.as_int) { + LOG(ERROR) << "expected: " << expected.value.as_int << " actual: " << + actual.value.as_int; + return false; + } + return true; + case PP_VARTYPE_DOUBLE: + if (fabs(expected.value.as_double - actual.value.as_double) > 1.0e-4) { + LOG(ERROR) << "expected: " << expected.value.as_double << + " actual: " << actual.value.as_double; + return false; + } + return true; + case PP_VARTYPE_OBJECT: + if (expected.value.as_id != actual.value.as_id) { + LOG(ERROR) << "expected: " << expected.value.as_id << " actual: " << + actual.value.as_id; + return false; + } + return true; + case PP_VARTYPE_STRING: { + StringVar* expected_var = StringVar::FromPPVar(expected); + StringVar* actual_var = StringVar::FromPPVar(actual); + DCHECK(expected_var && actual_var); + if (expected_var->value() != actual_var->value()) { + LOG(ERROR) << "expected: " << expected_var->value() << " actual: " << + actual_var->value(); + return false; + } + return true; + } + case PP_VARTYPE_ARRAY_BUFFER: { + ArrayBufferVar* expected_var = ArrayBufferVar::FromPPVar(expected); + ArrayBufferVar* actual_var = ArrayBufferVar::FromPPVar(actual); + DCHECK(expected_var && actual_var); + if (expected_var->ByteLength() != actual_var->ByteLength()) { + LOG(ERROR) << "expected: " << expected_var->ByteLength() << + " actual: " << actual_var->ByteLength(); + return false; + } + if (memcmp(expected_var->Map(), actual_var->Map(), + expected_var->ByteLength()) != 0) { + LOG(ERROR) << "expected array buffer does not match actual."; + return false; + } + return true; + } + case PP_VARTYPE_ARRAY: { + ArrayVar* expected_var = ArrayVar::FromPPVar(expected); + ArrayVar* actual_var = ArrayVar::FromPPVar(actual); + DCHECK(expected_var && actual_var); + if (expected_var->elements().size() != actual_var->elements().size()) { + LOG(ERROR) << "expected: " << expected_var->elements().size() << + " actual: " << actual_var->elements().size(); + return false; + } + for (size_t i = 0; i < expected_var->elements().size(); ++i) { + if (!Equals(expected_var->elements()[i].get(), + actual_var->elements()[i].get(), + visited_map)) { + return false; + } + } + return true; + } + case PP_VARTYPE_DICTIONARY: { + DictionaryVar* expected_var = DictionaryVar::FromPPVar(expected); + DictionaryVar* actual_var = DictionaryVar::FromPPVar(actual); + DCHECK(expected_var && actual_var); + if (expected_var->key_value_map().size() != + actual_var->key_value_map().size()) { + LOG(ERROR) << "expected: " << expected_var->key_value_map().size() << + " actual: " << actual_var->key_value_map().size(); + return false; + } + DictionaryVar::KeyValueMap::const_iterator expected_iter = + expected_var->key_value_map().begin(); + DictionaryVar::KeyValueMap::const_iterator actual_iter = + actual_var->key_value_map().begin(); + for ( ; expected_iter != expected_var->key_value_map().end(); + ++expected_iter, ++actual_iter) { + if (expected_iter->first != actual_iter->first) { + LOG(ERROR) << "expected: " << expected_iter->first << + " actual: " << actual_iter->first; + return false; + } + if (!Equals(expected_iter->second.get(), + actual_iter->second.get(), + visited_map)) { + return false; + } + } + return true; + } + } + NOTREACHED(); + return false; +} + +} // namespace + +bool TestEqual(const PP_Var& expected, const PP_Var& actual) { + base::hash_map<int64_t, int64_t> visited_map; + return Equals(expected, actual, &visited_map); +} + +} // namespace ppapi diff --git a/ppapi/shared_impl/unittest_utils.h b/ppapi/shared_impl/unittest_utils.h new file mode 100644 index 0000000..e07d8a8 --- /dev/null +++ b/ppapi/shared_impl/unittest_utils.h @@ -0,0 +1,19 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef PPAPI_SHARED_IMPL_UNITTEST_UTILS_H_ +#define PPAPI_SHARED_IMPL_UNITTEST_UTILS_H_ + +#include "ppapi/c/pp_var.h" + +namespace ppapi { + +// Compares two vars for equality. This is a deep comparison (the entire graph +// is traversed recursively). The function guarantees that the topology of the +// two PP_Var graphs being compared is identical, including graphs with cycles. +bool TestEqual(const PP_Var& expected, const PP_Var& actual); + +} // namespace ppapi + +#endif // PPAPI_SHARED_IMPL_UNITTEST_UTILS_H_ diff --git a/webkit/plugins/ppapi/v8_var_converter.cc b/webkit/plugins/ppapi/v8_var_converter.cc new file mode 100644 index 0000000..a8952ff --- /dev/null +++ b/webkit/plugins/ppapi/v8_var_converter.cc @@ -0,0 +1,423 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/plugins/ppapi/v8_var_converter.h" + +#include <map> +#include <stack> +#include <string> + +#include "base/hash_tables.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "ppapi/shared_impl/array_var.h" +#include "ppapi/shared_impl/dictionary_var.h" +#include "ppapi/shared_impl/var.h" +#include "ppapi/shared_impl/var_tracker.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebArrayBuffer.h" +#include "webkit/plugins/ppapi/host_array_buffer_var.h" + +using ppapi::ArrayBufferVar; +using ppapi::ArrayVar; +using ppapi::DictionaryVar; +using ppapi::ScopedPPVar; +using ppapi::StringVar; +using std::make_pair; + +namespace { + +struct HashedHandle { + HashedHandle(v8::Handle<v8::Object> h) : handle(h) {} + size_t hash() const { return handle->GetIdentityHash(); } + bool operator==(const HashedHandle& h) const { return handle == h.handle; } + bool operator<(const HashedHandle& h) const { return hash() < h.hash(); } + v8::Handle<v8::Object> handle; +}; + +} // namespace + +namespace BASE_HASH_NAMESPACE { +#if defined(COMPILER_GCC) +template <> +struct hash<HashedHandle> { + size_t operator()(const HashedHandle& handle) const { + return handle.hash(); + } +}; +#elif defined(COMPILER_MSVC) +inline size_t hash_value(const HashedHandle& handle) { + return handle.hash(); +} +#endif +} // namespace BASE_HASH_NAMESPACE + +namespace webkit { +namespace ppapi { + +namespace { + +// Maps PP_Var IDs to the V8 value handle they correspond to. +typedef base::hash_map<int64_t, v8::Handle<v8::Value> > VarHandleMap; + +// Maps V8 value handles to the PP_Var they correspond to. +typedef base::hash_map<HashedHandle, ScopedPPVar> HandleVarMap; + +// Returns a V8 value which corresponds to a given PP_Var. If |var| is a +// reference counted PP_Var type, and it exists in |visited_ids|, the V8 value +// associated with it in the map will be returned, otherwise a new V8 value will +// be created and added to the map. |did_create| indicates whether a new v8 +// value was created as a result of calling the function. +bool GetOrCreateV8Value(const PP_Var& var, + v8::Handle<v8::Value>* result, + bool* did_create, + VarHandleMap* visited_ids) { + *did_create = false; + + if (::ppapi::VarTracker::IsVarTypeRefcounted(var.type)) { + VarHandleMap::iterator it = visited_ids->find(var.value.as_id); + if (it != visited_ids->end()) { + *result = it->second; + return true; + } + } + + switch (var.type) { + case PP_VARTYPE_UNDEFINED: + *result = v8::Undefined(); + break; + case PP_VARTYPE_NULL: + *result = v8::Null(); + break; + case PP_VARTYPE_BOOL: + *result = (var.value.as_bool == PP_TRUE) ? v8::True() : v8::False(); + break; + case PP_VARTYPE_INT32: + *result = v8::Integer::New(var.value.as_int); + break; + case PP_VARTYPE_DOUBLE: + *result = v8::Number::New(var.value.as_double); + break; + case PP_VARTYPE_STRING: { + StringVar* string = StringVar::FromPPVar(var); + if (!string) { + NOTREACHED(); + result->Clear(); + return false; + } + const std::string& value = string->value(); + // Create a string object rather than a string primitive. This allows us + // to have multiple references to the same string in javascript, which + // matches the reference behavior of PP_Vars. + *result = v8::String::New(value.c_str(), value.size())->ToObject(); + break; + } + case PP_VARTYPE_ARRAY_BUFFER: { + ArrayBufferVar* buffer = ArrayBufferVar::FromPPVar(var); + if (!buffer) { + NOTREACHED(); + result->Clear(); + return false; + } + HostArrayBufferVar* host_buffer = + static_cast<HostArrayBufferVar*>(buffer); + *result = + v8::Local<v8::Value>::New(host_buffer->webkit_buffer().toV8Value()); + break; + } + case PP_VARTYPE_ARRAY: + *result = v8::Array::New(); + break; + case PP_VARTYPE_DICTIONARY: + *result = v8::Object::New(); + break; + case PP_VARTYPE_OBJECT: + NOTREACHED(); + result->Clear(); + return false; + } + + *did_create = true; + if (::ppapi::VarTracker::IsVarTypeRefcounted(var.type)) + (*visited_ids)[var.value.as_id] = *result; + return true; +} + +// For a given V8 value handle, this returns a PP_Var which corresponds to it. +// If the handle already exists in |visited_handles|, the PP_Var associated with +// it will be returned, otherwise a new V8 value will be created and added to +// the map. |did_create| indicates if a new PP_Var was created as a result of +// calling the function. +bool GetOrCreateVar(v8::Handle<v8::Value> val, + PP_Var* result, + bool* did_create, + HandleVarMap* visited_handles) { + CHECK(!val.IsEmpty()); + *did_create = false; + + // Even though every v8 string primitive encountered will be a unique object, + // we still add them to |visited_handles| so that the corresponding string + // PP_Var created will be properly refcounted. + if (val->IsObject() || val->IsString()) { + HandleVarMap::const_iterator it = visited_handles->find( + HashedHandle(val->ToObject())); + if (it != visited_handles->end()) { + *result = it->second.get(); + return true; + } + } + + if (val->IsUndefined()) { + *result = PP_MakeUndefined(); + } else if (val->IsNull()) { + *result = PP_MakeNull(); + } else if (val->IsBoolean() || val->IsBooleanObject()) { + *result = PP_MakeBool(PP_FromBool(val->ToBoolean()->Value())); + } else if (val->IsInt32()) { + *result = PP_MakeInt32(val->ToInt32()->Value()); + } else if (val->IsNumber() || val->IsNumberObject()) { + *result = PP_MakeDouble(val->ToNumber()->Value()); + } else if (val->IsString() || val->IsStringObject()) { + v8::String::Utf8Value utf8(val->ToString()); + *result = StringVar::StringToPPVar(std::string(*utf8, utf8.length())); + } else if (val->IsArray()) { + *result = (new ArrayVar())->GetPPVar(); + } else if (val->IsObject()) { + scoped_ptr<WebKit::WebArrayBuffer> web_array_buffer( + WebKit::WebArrayBuffer::createFromV8Value(val)); + if (web_array_buffer.get()) { + scoped_refptr<HostArrayBufferVar> buffer_var(new HostArrayBufferVar( + *web_array_buffer)); + *result = buffer_var->GetPPVar(); + } else { + *result = (new DictionaryVar())->GetPPVar(); + } + } else { + NOTREACHED(); + return false; + } + + *did_create = true; + if (val->IsObject() || val->IsString()) { + visited_handles->insert(make_pair( + HashedHandle(val->ToObject()), + ScopedPPVar(ScopedPPVar::PassRef(), *result))); + } + return true; +} + +} // namespace + +V8VarConverter::V8VarConverter() { +} + +bool V8VarConverter::ToV8Value(const PP_Var& var, + v8::Handle<v8::Context> context, + v8::Handle<v8::Value>* result) const { + v8::Context::Scope context_scope(context); + v8::HandleScope handle_scope; + + VarHandleMap visited_ids; + + std::stack<PP_Var> stack; + stack.push(var); + v8::Handle<v8::Value> root; + bool is_root = true; + + while (!stack.empty()) { + const PP_Var& current_var = stack.top(); + v8::Handle<v8::Value> current_v8; + stack.pop(); + bool did_create = false; + if (!GetOrCreateV8Value(current_var, ¤t_v8, &did_create, + &visited_ids)) { + return false; + } + + if (is_root) { + is_root = false; + root = current_v8; + } + + // Add child nodes to the stack. + if (current_var.type == PP_VARTYPE_ARRAY) { + ArrayVar* array_var = ArrayVar::FromPPVar(current_var); + if (!array_var) { + NOTREACHED(); + return false; + } + DCHECK(current_v8->IsArray()); + v8::Handle<v8::Array> v8_array = current_v8.As<v8::Array>(); + + for (size_t i = 0; i < array_var->elements().size(); ++i) { + const PP_Var& child_var = array_var->elements()[i].get(); + v8::Handle<v8::Value> child_v8; + if (!GetOrCreateV8Value(child_var, &child_v8, &did_create, + &visited_ids)) { + return false; + } + if (did_create && + (child_var.type == PP_VARTYPE_DICTIONARY || + child_var.type == PP_VARTYPE_ARRAY)) { + stack.push(child_var); + } + v8::TryCatch try_catch; + v8_array->Set(static_cast<uint32>(i), child_v8); + if (try_catch.HasCaught()) { + LOG(ERROR) << "Setter for index " << i << " threw an exception."; + return false; + } + } + } else if (current_var.type == PP_VARTYPE_DICTIONARY) { + DictionaryVar* dict_var = DictionaryVar::FromPPVar(current_var); + if (!dict_var) { + NOTREACHED(); + return false; + } + DCHECK(current_v8->IsObject()); + v8::Handle<v8::Object> v8_object = current_v8->ToObject(); + + for (DictionaryVar::KeyValueMap::const_iterator iter = + dict_var->key_value_map().begin(); + iter != dict_var->key_value_map().end(); + ++iter) { + const std::string& key = iter->first; + const PP_Var& child_var = iter->second.get(); + v8::Handle<v8::Value> child_v8; + if (!GetOrCreateV8Value(child_var, &child_v8, &did_create, + &visited_ids)) { + return false; + } + if (did_create && + (child_var.type == PP_VARTYPE_DICTIONARY || + child_var.type == PP_VARTYPE_ARRAY)) { + stack.push(child_var); + } + v8::TryCatch try_catch; + v8_object->Set(v8::String::New(key.c_str(), key.length()), child_v8); + if (try_catch.HasCaught()) { + LOG(ERROR) << "Setter for property " << key.c_str() << " threw an " + << "exception."; + return false; + } + } + } + } + + *result = handle_scope.Close(root); + return true; +} + +bool V8VarConverter::FromV8Value(v8::Handle<v8::Value> val, + v8::Handle<v8::Context> context, + PP_Var* result) const { + v8::Context::Scope context_scope(context); + v8::HandleScope handle_scope; + + HandleVarMap visited_handles; + + std::stack<v8::Handle<v8::Value> > stack; + stack.push(val); + ScopedPPVar root; + bool is_root = true; + + while (!stack.empty()) { + v8::Handle<v8::Value> current_v8 = stack.top(); + PP_Var current_var; + stack.pop(); + bool did_create = false; + if (!GetOrCreateVar(current_v8, ¤t_var, &did_create, + &visited_handles)) { + return false; + } + + if (is_root) { + is_root = false; + root = current_var; + } + + // Add child nodes to the stack. + if (current_var.type == PP_VARTYPE_ARRAY) { + DCHECK(current_v8->IsArray()); + v8::Handle<v8::Array> v8_array = current_v8.As<v8::Array>(); + + ArrayVar* array_var = ArrayVar::FromPPVar(current_var); + if (!array_var) { + NOTREACHED(); + return false; + } + + for (uint32 i = 0; i < v8_array->Length(); ++i) { + v8::TryCatch try_catch; + v8::Handle<v8::Value> child_v8 = v8_array->Get(i); + if (try_catch.HasCaught()) + return false; + + if (!v8_array->HasRealIndexedProperty(i)) + continue; + + PP_Var child_var; + if (!GetOrCreateVar(child_v8, &child_var, &did_create, + &visited_handles)) { + // Silently ignore the case where we can't convert to a Var as we may + // be trying to convert a type that doesn't have a corresponding + // PP_Var type. + continue; + } + if (did_create && child_v8->IsObject()) + stack.push(child_v8); + + array_var->Set(i, child_var); + } + } else if (current_var.type == PP_VARTYPE_DICTIONARY) { + DCHECK(current_v8->IsObject()); + v8::Handle<v8::Object> v8_object = current_v8->ToObject(); + + DictionaryVar* dict_var = DictionaryVar::FromPPVar(current_var); + if (!dict_var) { + NOTREACHED(); + return false; + } + + v8::Handle<v8::Array> property_names(v8_object->GetOwnPropertyNames()); + for (uint32 i = 0; i < property_names->Length(); ++i) { + v8::Handle<v8::Value> key(property_names->Get(i)); + + // Extend this test to cover more types as necessary and if sensible. + if (!key->IsString() && !key->IsNumber()) { + NOTREACHED() << "Key \"" << *v8::String::AsciiValue(key) << "\" " + "is neither a string nor a number"; + return false; + } + + // Skip all callbacks: crbug.com/139933 + if (v8_object->HasRealNamedCallbackProperty(key->ToString())) + continue; + + v8::String::Utf8Value name_utf8(key->ToString()); + + v8::TryCatch try_catch; + v8::Handle<v8::Value> child_v8 = v8_object->Get(key); + if (try_catch.HasCaught()) + return false; + + PP_Var child_var; + if (!GetOrCreateVar(child_v8, &child_var, &did_create, + &visited_handles)) { + continue; + } + if (did_create && child_v8->IsObject()) + stack.push(child_v8); + + bool success = dict_var->SetWithStringKey( + std::string(*name_utf8, name_utf8.length()), child_var); + DCHECK(success); + } + } + } + *result = root.Release(); + return true; +} + +} // namespace ppapi +} // namespace webkit diff --git a/webkit/plugins/ppapi/v8_var_converter.h b/webkit/plugins/ppapi/v8_var_converter.h new file mode 100644 index 0000000..c19644c --- /dev/null +++ b/webkit/plugins/ppapi/v8_var_converter.h @@ -0,0 +1,41 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_PLUGINS_PPAPI_V8_VAR_CONVERTER_H +#define WEBKIT_PLUGINS_PPAPI_V8_VAR_CONVERTER_H + + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "ppapi/c/pp_var.h" +#include "v8/include/v8.h" +#include "webkit/plugins/webkit_plugins_export.h" + +namespace webkit { +namespace ppapi { + +// Class to convert between PP_Vars and V8 values. +class WEBKIT_PLUGINS_EXPORT V8VarConverter { + public: + V8VarConverter(); + + // Converts the given PP_Var to a v8::Value. True is returned upon success. + bool ToV8Value(const PP_Var& var, + v8::Handle<v8::Context> context, + v8::Handle<v8::Value>* result) const; + // Converts the given v8::Value to a PP_Var. True is returned upon success. + // Every PP_Var in the reference graph of which |result| is apart will have + // a refcount equal to the number of references to it in the graph. |result| + // will have one additional reference. + bool FromV8Value(v8::Handle<v8::Value> val, + v8::Handle<v8::Context> context, + PP_Var* result) const; + + DISALLOW_COPY_AND_ASSIGN(V8VarConverter); +}; + +} // namespace ppapi +} // namespace webkit + +#endif // WEBKIT_PLUGINS_PPAPI_V8_VAR_CONVERTER_H diff --git a/webkit/plugins/ppapi/v8_var_converter_unittest.cc b/webkit/plugins/ppapi/v8_var_converter_unittest.cc new file mode 100644 index 0000000..a216348 --- /dev/null +++ b/webkit/plugins/ppapi/v8_var_converter_unittest.cc @@ -0,0 +1,352 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/plugins/ppapi/v8_var_converter.h" + +#include <cmath> + +#include "base/logging.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/values.h" +#include "ppapi/c/pp_bool.h" +#include "ppapi/c/pp_var.h" +#include "ppapi/shared_impl/array_var.h" +#include "ppapi/shared_impl/dictionary_var.h" +#include "ppapi/shared_impl/ppapi_globals.h" +#include "ppapi/shared_impl/proxy_lock.h" +#include "ppapi/shared_impl/scoped_pp_var.h" +#include "ppapi/shared_impl/test_globals.h" +#include "ppapi/shared_impl/unittest_utils.h" +#include "ppapi/shared_impl/var.h" +#include "ppapi/shared_impl/var_tracker.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "v8/include/v8.h" + +using ppapi::ArrayBufferVar; +using ppapi::ArrayVar; +using ppapi::DictionaryVar; +using ppapi::PpapiGlobals; +using ppapi::ProxyLock; +using ppapi::ScopedPPVar; +using ppapi::StringVar; +using ppapi::TestGlobals; +using ppapi::TestEqual; +using ppapi::VarTracker; + +namespace webkit { +namespace ppapi { + +namespace { + +// Maps PP_Var IDs to the V8 value handle they correspond to. +typedef base::hash_map<int64_t, v8::Handle<v8::Value> > VarHandleMap; + +bool Equals(const PP_Var& var, + v8::Handle<v8::Value> val, + VarHandleMap* visited_ids) { + if (::ppapi::VarTracker::IsVarTypeRefcounted(var.type)) { + VarHandleMap::iterator it = visited_ids->find(var.value.as_id); + if (it != visited_ids->end()) + return it->second == val; + (*visited_ids)[var.value.as_id] = val; + } + + if (val->IsUndefined()) { + return var.type == PP_VARTYPE_UNDEFINED; + } else if (val->IsNull()) { + return var.type == PP_VARTYPE_NULL; + } else if (val->IsBoolean() || val->IsBooleanObject()) { + return var.type == PP_VARTYPE_BOOL && + PP_FromBool(val->ToBoolean()->Value()) == var.value.as_bool; + } else if (val->IsInt32()) { + return var.type == PP_VARTYPE_INT32 && + val->ToInt32()->Value() == var.value.as_int; + } else if (val->IsNumber() || val->IsNumberObject()) { + return var.type == PP_VARTYPE_DOUBLE && + fabs(val->ToNumber()->Value() - var.value.as_double) <= 1.0e-4; + } else if (val->IsString() || val->IsStringObject()) { + if (var.type != PP_VARTYPE_STRING) + return false; + StringVar* string_var = StringVar::FromPPVar(var); + DCHECK(string_var); + v8::String::Utf8Value utf8(val->ToString()); + return std::string(*utf8, utf8.length()) == string_var->value(); + } else if (val->IsArray()) { + if (var.type != PP_VARTYPE_ARRAY) + return false; + ArrayVar* array_var = ArrayVar::FromPPVar(var); + DCHECK(array_var); + v8::Handle<v8::Array> v8_array = val.As<v8::Array>(); + if (v8_array->Length() != array_var->elements().size()) + return false; + for (uint32 i = 0; i < v8_array->Length(); ++i) { + v8::Handle<v8::Value> child_v8 = v8_array->Get(i); + if (!Equals(array_var->elements()[i].get(), child_v8, visited_ids)) + return false; + } + return true; + } else if (val->IsObject()) { + if (var.type == PP_VARTYPE_ARRAY_BUFFER) { + // TODO(raymes): Implement this when we have tests for array buffers. + NOTIMPLEMENTED(); + return false; + } else { + v8::Handle<v8::Object> v8_object = val->ToObject(); + if (var.type != PP_VARTYPE_DICTIONARY) + return false; + DictionaryVar* dict_var = DictionaryVar::FromPPVar(var); + DCHECK(dict_var); + v8::Handle<v8::Array> property_names(v8_object->GetOwnPropertyNames()); + if (property_names->Length() != dict_var->key_value_map().size()) + return false; + for (uint32 i = 0; i < property_names->Length(); ++i) { + v8::Handle<v8::Value> key(property_names->Get(i)); + + if (!key->IsString() && !key->IsNumber()) + return false; + v8::Handle<v8::Value> child_v8 = v8_object->Get(key); + + v8::String::Utf8Value name_utf8(key->ToString()); + ScopedPPVar release_key(ScopedPPVar::PassRef(), + StringVar::StringToPPVar( + std::string(*name_utf8, name_utf8.length()))); + if (!dict_var->HasKey(release_key.get())) + return false; + ScopedPPVar release_value(ScopedPPVar::PassRef(), + dict_var->Get(release_key.get())); + if (!Equals(release_value.get(), child_v8, visited_ids)) + return false; + } + return true; + } + } + return false; +} + +bool Equals(const PP_Var& var, + v8::Handle<v8::Value> val) { + VarHandleMap var_handle_map; + return Equals(var, val, &var_handle_map); +} + +class V8VarConverterTest : public testing::Test { + public: + V8VarConverterTest() {} + ~V8VarConverterTest() {} + + // testing::Test implementation. + virtual void SetUp() { + ProxyLock::Acquire(); + v8::HandleScope handle_scope; + v8::Handle<v8::ObjectTemplate> global = v8::ObjectTemplate::New(); + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + // TODO(marja): Use v8::Persistent::Reset here. + context_ = v8::Persistent<v8::Context>( + isolate, v8::Context::New(isolate, NULL, global)); + } + virtual void TearDown() { + context_.Dispose(context_->GetIsolate()); + ASSERT_TRUE(PpapiGlobals::Get()->GetVarTracker()->GetLiveVars().empty()); + ProxyLock::Release(); + } + + protected: + bool RoundTrip(const PP_Var& var, PP_Var* result) { + V8VarConverter converter; + v8::Context::Scope context_scope(context_); + v8::HandleScope handle_scope; + v8::Handle<v8::Value> v8_result; + if (!converter.ToV8Value(var, context_, &v8_result)) + return false; + if (!Equals(var, v8_result)) + return false; + if (!converter.FromV8Value(v8_result, context_, result)) + return false; + return true; + } + + // Assumes a ref for var. + bool RoundTripAndCompare(const PP_Var& var) { + ScopedPPVar expected(ScopedPPVar::PassRef(), var); + PP_Var actual_var; + if (!RoundTrip(expected.get(), &actual_var)) + return false; + ScopedPPVar actual(ScopedPPVar::PassRef(), actual_var); + return TestEqual(expected.get(), actual.get()); + } + + // Context for the JavaScript in the test. + v8::Persistent<v8::Context> context_; + + private: + TestGlobals globals_; +}; + +} // namespace + +TEST_F(V8VarConverterTest, SimpleRoundTripTest) { + EXPECT_TRUE(RoundTripAndCompare(PP_MakeUndefined())); + EXPECT_TRUE(RoundTripAndCompare(PP_MakeNull())); + EXPECT_TRUE(RoundTripAndCompare(PP_MakeInt32(100))); + EXPECT_TRUE(RoundTripAndCompare(PP_MakeBool(PP_TRUE))); + EXPECT_TRUE(RoundTripAndCompare(PP_MakeDouble(53.75))); +} + +TEST_F(V8VarConverterTest, StringRoundTripTest) { + EXPECT_TRUE(RoundTripAndCompare(StringVar::StringToPPVar(""))); + EXPECT_TRUE(RoundTripAndCompare(StringVar::StringToPPVar("hello world!"))); +} + +TEST_F(V8VarConverterTest, ArrayBufferRoundTripTest) { + // TODO(raymes): Testing this here requires spinning up some of WebKit. + // Work out how to do this. +} + +TEST_F(V8VarConverterTest, DictionaryArrayRoundTripTest) { + // Empty array. + scoped_refptr<ArrayVar> array(new ArrayVar); + ScopedPPVar release_array(ScopedPPVar::PassRef(), array->GetPPVar()); + EXPECT_TRUE(RoundTripAndCompare(array->GetPPVar())); + + size_t index = 0; + + // Array with primitives. + array->Set(index++, PP_MakeUndefined()); + array->Set(index++, PP_MakeNull()); + array->Set(index++, PP_MakeInt32(100)); + array->Set(index++, PP_MakeBool(PP_FALSE)); + array->Set(index++, PP_MakeDouble(0.123)); + EXPECT_TRUE(RoundTripAndCompare(array->GetPPVar())); + + // Array with 2 references to the same string. + ScopedPPVar release_string( + ScopedPPVar::PassRef(), StringVar::StringToPPVar("abc")); + array->Set(index++, release_string.get()); + array->Set(index++, release_string.get()); + EXPECT_TRUE(RoundTripAndCompare(array->GetPPVar())); + + // Array with nested array that references the same string. + scoped_refptr<ArrayVar> array2(new ArrayVar); + ScopedPPVar release_array2(ScopedPPVar::PassRef(), array2->GetPPVar()); + array2->Set(0, release_string.get()); + array->Set(index++, release_array2.get()); + EXPECT_TRUE(RoundTripAndCompare(array->GetPPVar())); + + // Empty dictionary. + scoped_refptr<DictionaryVar> dictionary(new DictionaryVar); + ScopedPPVar release_dictionary(ScopedPPVar::PassRef(), + dictionary->GetPPVar()); + EXPECT_TRUE(RoundTripAndCompare(dictionary->GetPPVar())); + + // Dictionary with primitives. + dictionary->SetWithStringKey("1", PP_MakeUndefined()); + dictionary->SetWithStringKey("2", PP_MakeNull()); + dictionary->SetWithStringKey("3", PP_MakeInt32(-100)); + dictionary->SetWithStringKey("4", PP_MakeBool(PP_TRUE)); + dictionary->SetWithStringKey("5", PP_MakeDouble(-103.52)); + EXPECT_TRUE(RoundTripAndCompare(dictionary->GetPPVar())); + + // Dictionary with 2 references to the same string. + dictionary->SetWithStringKey("6", release_string.get()); + dictionary->SetWithStringKey("7", release_string.get()); + EXPECT_TRUE(RoundTripAndCompare(dictionary->GetPPVar())); + + // Dictionary with nested dictionary that references the same string. + scoped_refptr<DictionaryVar> dictionary2(new DictionaryVar); + ScopedPPVar release_dictionary2(ScopedPPVar::PassRef(), + dictionary2->GetPPVar()); + dictionary2->SetWithStringKey("abc", release_string.get()); + dictionary->SetWithStringKey("8", release_dictionary2.get()); + EXPECT_TRUE(RoundTripAndCompare(dictionary->GetPPVar())); + + // Array with dictionary. + array->Set(index++, release_dictionary.get()); + EXPECT_TRUE(RoundTripAndCompare(array->GetPPVar())); + + // Array with dictionary with array. + array2->Set(0, PP_MakeInt32(100)); + dictionary->SetWithStringKey("9", release_array2.get()); + EXPECT_TRUE(RoundTripAndCompare(array->GetPPVar())); + + // Array <-> dictionary cycle. + dictionary->SetWithStringKey("10", release_array.get()); + PP_Var result_var; + EXPECT_TRUE(RoundTrip(release_dictionary.get(), &result_var)); + ScopedPPVar result = ScopedPPVar(ScopedPPVar::PassRef(), result_var); + EXPECT_TRUE(TestEqual(release_dictionary.get(), result.get())); + // Break the cycle. + // TODO(raymes): We need some better machinery for releasing vars with + // cycles. Remove the code below once we have that. + dictionary->DeleteWithStringKey("10"); + DictionaryVar* result_dictionary = DictionaryVar::FromPPVar(result.get()); + result_dictionary->DeleteWithStringKey("10"); + + // Array with self references. + array->Set(index, release_array.get()); + EXPECT_TRUE(RoundTrip(release_array.get(), &result_var)); + result = ScopedPPVar(ScopedPPVar::PassRef(), result_var); + EXPECT_TRUE(TestEqual(release_array.get(), result.get())); + // Break the self reference. + array->Set(index, PP_MakeUndefined()); + ArrayVar* result_array = ArrayVar::FromPPVar(result.get()); + result_array->Set(index, PP_MakeUndefined()); +} + +TEST_F(V8VarConverterTest, StrangeDictionaryKeyTest) { + { + // Test keys with '.'. + scoped_refptr<DictionaryVar> dictionary(new DictionaryVar); + dictionary->SetWithStringKey(".", PP_MakeUndefined()); + dictionary->SetWithStringKey("x.y", PP_MakeUndefined()); + EXPECT_TRUE(RoundTripAndCompare(dictionary->GetPPVar())); + } + + { + // Test non-string key types. They should be cast to strings. + v8::Context::Scope context_scope(context_); + v8::HandleScope handle_scope; + + const char* source = "(function() {" + "return {" + "1: 'foo'," + "'2': 'bar'," + "true: 'baz'," + "false: 'qux'," + "null: 'quux'," + "undefined: 'oops'" + "};" + "})();"; + + v8::Handle<v8::Script> script(v8::Script::New(v8::String::New(source))); + v8::Handle<v8::Object> object = script->Run().As<v8::Object>(); + ASSERT_FALSE(object.IsEmpty()); + + V8VarConverter converter; + PP_Var actual; + ASSERT_TRUE(converter.FromV8Value(object, context_, &actual)); + ScopedPPVar release_actual(ScopedPPVar::PassRef(), actual); + + scoped_refptr<DictionaryVar> expected(new DictionaryVar); + ScopedPPVar foo(ScopedPPVar::PassRef(), StringVar::StringToPPVar("foo")); + expected->SetWithStringKey("1", foo.get()); + ScopedPPVar bar(ScopedPPVar::PassRef(), StringVar::StringToPPVar("bar")); + expected->SetWithStringKey("2", bar.get()); + ScopedPPVar baz(ScopedPPVar::PassRef(), StringVar::StringToPPVar("baz")); + expected->SetWithStringKey("true", baz.get()); + ScopedPPVar qux(ScopedPPVar::PassRef(), StringVar::StringToPPVar("qux")); + expected->SetWithStringKey("false", qux.get()); + ScopedPPVar quux(ScopedPPVar::PassRef(), StringVar::StringToPPVar("quux")); + expected->SetWithStringKey("null", quux.get()); + ScopedPPVar oops(ScopedPPVar::PassRef(), StringVar::StringToPPVar("oops")); + expected->SetWithStringKey("undefined", oops.get()); + ScopedPPVar release_expected( + ScopedPPVar::PassRef(), expected->GetPPVar()); + + ASSERT_TRUE(TestEqual(release_expected.get(), release_actual.get())); + } +} + +} // namespace ppapi +} // namespace webkit diff --git a/webkit/plugins/webkit_plugins.gypi b/webkit/plugins/webkit_plugins.gypi index a301783..1ab1831 100644 --- a/webkit/plugins/webkit_plugins.gypi +++ b/webkit/plugins/webkit_plugins.gypi @@ -204,6 +204,8 @@ '../plugins/ppapi/usb_key_code_conversion_linux.cc', '../plugins/ppapi/usb_key_code_conversion_mac.cc', '../plugins/ppapi/usb_key_code_conversion_win.cc', + '../plugins/ppapi/v8_var_converter.cc', + '../plugins/ppapi/v8_var_converter.h', '../plugins/sad_plugin.cc', '../plugins/sad_plugin.h', '../plugins/webkit_plugins_export.h', |