summaryrefslogtreecommitdiffstats
path: root/webkit/plugins/ppapi
diff options
context:
space:
mode:
authorraymes@chromium.org <raymes@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-05-16 18:03:57 +0000
committerraymes@chromium.org <raymes@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-05-16 18:03:57 +0000
commit7e3461079b02605faf2a1e767bfad03bb5e8eaf1 (patch)
tree951e1edba29f6461f74cfb6564383e6183148d3f /webkit/plugins/ppapi
parent05893fabc7515cb9fc253ef5a9822d3325e6d344 (diff)
downloadchromium_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
Diffstat (limited to 'webkit/plugins/ppapi')
-rw-r--r--webkit/plugins/ppapi/v8_var_converter.cc423
-rw-r--r--webkit/plugins/ppapi/v8_var_converter.h41
-rw-r--r--webkit/plugins/ppapi/v8_var_converter_unittest.cc352
3 files changed, 816 insertions, 0 deletions
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, &current_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, &current_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