blob: 88d645ad19769b68d9342eb0334e39df32f4c986 [file] [log] [blame]
// 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/var_value_conversions.h"
#include <cmath>
#include <cstring>
#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/var.h"
#include "ppapi/shared_impl/var_tracker.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace ppapi {
namespace {
bool Equals(const base::Value& value, const PP_Var& var) {
switch (value.GetType()) {
case base::Value::TYPE_NULL: {
return var.type == PP_VARTYPE_NULL || var.type == PP_VARTYPE_UNDEFINED;
}
case base::Value::TYPE_BOOLEAN: {
bool result = false;
return var.type == PP_VARTYPE_BOOL &&
value.GetAsBoolean(&result) &&
result == PP_ToBool(var.value.as_bool);
}
case base::Value::TYPE_INTEGER: {
int result = 0;
return var.type == PP_VARTYPE_INT32 &&
value.GetAsInteger(&result) &&
result == var.value.as_int;
}
case base::Value::TYPE_DOUBLE: {
double result = 0;
return var.type == PP_VARTYPE_DOUBLE &&
value.GetAsDouble(&result) &&
fabs(result - var.value.as_double) < 1.0e-4;
}
case base::Value::TYPE_STRING: {
std::string result;
StringVar* string_var = StringVar::FromPPVar(var);
return string_var &&
value.GetAsString(&result) &&
result == string_var->value();
}
case base::Value::TYPE_BINARY: {
const base::BinaryValue& binary_value =
static_cast<const base::BinaryValue&>(value);
ArrayBufferVar* array_buffer_var = ArrayBufferVar::FromPPVar(var);
if (!array_buffer_var ||
binary_value.GetSize() != array_buffer_var->ByteLength()) {
return false;
}
bool result = !memcmp(binary_value.GetBuffer(), array_buffer_var->Map(),
binary_value.GetSize());
array_buffer_var->Unmap();
return result;
}
case base::Value::TYPE_DICTIONARY: {
const base::DictionaryValue& dict_value =
static_cast<const base::DictionaryValue&>(value);
DictionaryVar* dict_var = DictionaryVar::FromPPVar(var);
if (!dict_var)
return false;
size_t count = 0;
for (DictionaryVar::KeyValueMap::const_iterator iter =
dict_var->key_value_map().begin();
iter != dict_var->key_value_map().end();
++iter) {
if (iter->second.get().type == PP_VARTYPE_UNDEFINED ||
iter->second.get().type == PP_VARTYPE_NULL) {
continue;
}
++count;
const base::Value* sub_value = NULL;
if (!dict_value.GetWithoutPathExpansion(iter->first, &sub_value) ||
!Equals(*sub_value, iter->second.get())) {
return false;
}
}
return count == dict_value.size();
}
case base::Value::TYPE_LIST: {
const base::ListValue& list_value =
static_cast<const base::ListValue&>(value);
ArrayVar* array_var = ArrayVar::FromPPVar(var);
if (!array_var || list_value.GetSize() != array_var->elements().size())
return false;
base::ListValue::const_iterator value_iter = list_value.begin();
ArrayVar::ElementVector::const_iterator var_iter =
array_var->elements().begin();
for (; value_iter != list_value.end() &&
var_iter != array_var->elements().end();
++value_iter, ++var_iter) {
if (!Equals(**value_iter, var_iter->get()))
return false;
}
return true;
}
}
NOTREACHED();
return false;
}
bool ConvertVarAndVerify(const PP_Var& var) {
scoped_ptr<base::Value> value(CreateValueFromVar(var));
if (value.get())
return Equals(*value, var);
return false;
}
bool ConvertValueAndVerify(const base::Value& value) {
ScopedPPVar var(ScopedPPVar::PassRef(), CreateVarFromValue(value));
if (var.get().type != PP_VARTYPE_UNDEFINED)
return Equals(value, var.get());
return false;
}
class VarValueConversionsTest : public testing::Test {
public:
VarValueConversionsTest() {
}
virtual ~VarValueConversionsTest() {
}
// testing::Test implementation.
virtual void SetUp() {
ProxyLock::Acquire();
}
virtual void TearDown() {
ASSERT_TRUE(PpapiGlobals::Get()->GetVarTracker()->GetLiveVars().empty());
ProxyLock::Release();
}
private:
TestGlobals globals_;
};
} // namespace
TEST_F(VarValueConversionsTest, CreateValueFromVar) {
{
// Var holding a ref to itself is not a valid input.
scoped_refptr<DictionaryVar> dict_var(new DictionaryVar());
ScopedPPVar var_1(ScopedPPVar::PassRef(), dict_var->GetPPVar());
scoped_refptr<ArrayVar> array_var(new ArrayVar());
ScopedPPVar var_2(ScopedPPVar::PassRef(), array_var->GetPPVar());
ASSERT_TRUE(dict_var->SetWithStringKey("key_1", var_2.get()));
ASSERT_TRUE(ConvertVarAndVerify(var_1.get()));
ASSERT_TRUE(array_var->Set(0, var_1.get()));
scoped_ptr<base::Value> value(CreateValueFromVar(var_1.get()));
ASSERT_EQ(NULL, value.get());
// Make sure |var_1| doesn't indirectly hold a ref to itself, otherwise it
// is leaked.
dict_var->DeleteWithStringKey("key_1");
}
// Vars of null or undefined type are converted to null values.
{
ASSERT_TRUE(ConvertVarAndVerify(PP_MakeNull()));
ASSERT_TRUE(ConvertVarAndVerify(PP_MakeUndefined()));
}
{
// Test empty dictionary.
scoped_refptr<DictionaryVar> dict_var(new DictionaryVar());
ScopedPPVar var(ScopedPPVar::PassRef(), dict_var->GetPPVar());
ASSERT_TRUE(ConvertVarAndVerify(var.get()));
}
{
// Key-value pairs whose value is undefined or null are ignored.
scoped_refptr<DictionaryVar> dict_var(new DictionaryVar());
ASSERT_TRUE(dict_var->SetWithStringKey("key_1", PP_MakeUndefined()));
ASSERT_TRUE(dict_var->SetWithStringKey("key_2", PP_MakeInt32(1)));
ASSERT_TRUE(dict_var->SetWithStringKey("key_3", PP_MakeNull()));
ScopedPPVar var(ScopedPPVar::PassRef(), dict_var->GetPPVar());
ASSERT_TRUE(ConvertVarAndVerify(var.get()));
}
{
// The same PP_Var is allowed to appear multiple times.
scoped_refptr<DictionaryVar> dict_var_1(new DictionaryVar());
ScopedPPVar dict_pp_var_1(ScopedPPVar::PassRef(), dict_var_1->GetPPVar());
scoped_refptr<DictionaryVar> dict_var_2(new DictionaryVar());
ScopedPPVar dict_pp_var_2(ScopedPPVar::PassRef(), dict_var_2->GetPPVar());
scoped_refptr<StringVar> string_var(new StringVar("string_value"));
ScopedPPVar string_pp_var(ScopedPPVar::PassRef(), string_var->GetPPVar());
ASSERT_TRUE(dict_var_1->SetWithStringKey("key_1", dict_pp_var_2.get()));
ASSERT_TRUE(dict_var_1->SetWithStringKey("key_2", dict_pp_var_2.get()));
ASSERT_TRUE(dict_var_1->SetWithStringKey("key_3", string_pp_var.get()));
ASSERT_TRUE(dict_var_2->SetWithStringKey("key_4", string_pp_var.get()));
ASSERT_TRUE(ConvertVarAndVerify(dict_pp_var_1.get()));
}
{
// Test basic cases for array.
scoped_refptr<ArrayVar> array_var(new ArrayVar());
ScopedPPVar var(ScopedPPVar::PassRef(), array_var->GetPPVar());
ASSERT_TRUE(ConvertVarAndVerify(var.get()));
ASSERT_TRUE(array_var->Set(0, PP_MakeDouble(1)));
ASSERT_TRUE(ConvertVarAndVerify(var.get()));
}
{
// Test more complex inputs.
scoped_refptr<DictionaryVar> dict_var_1(new DictionaryVar());
ScopedPPVar dict_pp_var_1(ScopedPPVar::PassRef(), dict_var_1->GetPPVar());
scoped_refptr<DictionaryVar> dict_var_2(new DictionaryVar());
ScopedPPVar dict_pp_var_2(ScopedPPVar::PassRef(), dict_var_2->GetPPVar());
scoped_refptr<ArrayVar> array_var(new ArrayVar());
ScopedPPVar array_pp_var(ScopedPPVar::PassRef(), array_var->GetPPVar());
scoped_refptr<StringVar> string_var(new StringVar("string_value"));
ScopedPPVar string_pp_var(ScopedPPVar::PassRef(), string_var->GetPPVar());
ASSERT_TRUE(dict_var_1->SetWithStringKey("null_key", PP_MakeNull()));
ASSERT_TRUE(dict_var_1->SetWithStringKey("string_key",
string_pp_var.get()));
ASSERT_TRUE(dict_var_1->SetWithStringKey("dict_key", dict_pp_var_2.get()));
ASSERT_TRUE(dict_var_2->SetWithStringKey("undefined_key",
PP_MakeUndefined()));
ASSERT_TRUE(dict_var_2->SetWithStringKey("double_key", PP_MakeDouble(1)));
ASSERT_TRUE(dict_var_2->SetWithStringKey("array_key", array_pp_var.get()));
ASSERT_TRUE(array_var->Set(0, PP_MakeInt32(2)));
ASSERT_TRUE(array_var->Set(1, PP_MakeBool(PP_TRUE)));
ASSERT_TRUE(array_var->SetLength(4));
ASSERT_TRUE(ConvertVarAndVerify(dict_pp_var_1.get()));
}
{
// Test that dictionary keys containing '.' are handled correctly.
scoped_refptr<DictionaryVar> dict_var(new DictionaryVar());
ScopedPPVar dict_pp_var(ScopedPPVar::PassRef(), dict_var->GetPPVar());
ASSERT_TRUE(dict_var->SetWithStringKey("double.key", PP_MakeDouble(1)));
ASSERT_TRUE(dict_var->SetWithStringKey("int.key..name", PP_MakeInt32(2)));
ASSERT_TRUE(ConvertVarAndVerify(dict_pp_var.get()));
}
}
TEST_F(VarValueConversionsTest, CreateVarFromValue) {
{
// Test basic cases for dictionary.
base::DictionaryValue dict_value;
ASSERT_TRUE(ConvertValueAndVerify(dict_value));
dict_value.SetInteger("int_key", 1);
ASSERT_TRUE(ConvertValueAndVerify(dict_value));
}
{
// Test basic cases for array.
base::ListValue list_value;
ASSERT_TRUE(ConvertValueAndVerify(list_value));
list_value.AppendInteger(1);
ASSERT_TRUE(ConvertValueAndVerify(list_value));
}
{
// Test more complex inputs.
base::DictionaryValue dict_value;
dict_value.SetString("string_key", "string_value");
dict_value.SetDouble("dict_key.double_key", 1);
scoped_ptr<base::ListValue> list_value(new base::ListValue());
list_value->AppendInteger(2);
list_value->AppendBoolean(true);
list_value->Append(base::Value::CreateNullValue());
dict_value.Set("dict_key.array_key", list_value.release());
ASSERT_TRUE(ConvertValueAndVerify(dict_value));
}
}
TEST_F(VarValueConversionsTest, CreateListValueFromVarVector) {
{
// Test empty var vector.
scoped_ptr<base::ListValue> list_value(
CreateListValueFromVarVector(std::vector<PP_Var>()));
ASSERT_TRUE(list_value.get());
ASSERT_EQ(0u, list_value->GetSize());
}
{
// Test more complex inputs.
scoped_refptr<StringVar> string_var(new StringVar("string_value"));
ScopedPPVar string_pp_var(ScopedPPVar::PassRef(), string_var->GetPPVar());
scoped_refptr<DictionaryVar> dict_var(new DictionaryVar());
ScopedPPVar dict_pp_var(ScopedPPVar::PassRef(), dict_var->GetPPVar());
ASSERT_TRUE(dict_var->SetWithStringKey("null_key", PP_MakeNull()));
ASSERT_TRUE(dict_var->SetWithStringKey("string_key", string_pp_var.get()));
scoped_refptr<ArrayVar> array_var(new ArrayVar());
ScopedPPVar array_pp_var(ScopedPPVar::PassRef(), array_var->GetPPVar());
ASSERT_TRUE(array_var->Set(0, PP_MakeInt32(2)));
ASSERT_TRUE(array_var->Set(1, PP_MakeBool(PP_TRUE)));
ASSERT_TRUE(array_var->SetLength(4));
std::vector<PP_Var> vars;
vars.push_back(dict_pp_var.get());
vars.push_back(string_pp_var.get());
vars.push_back(array_pp_var.get());
vars.push_back(PP_MakeDouble(1));
vars.push_back(PP_MakeUndefined());
vars.push_back(PP_MakeNull());
scoped_ptr<base::ListValue> list_value(CreateListValueFromVarVector(vars));
ASSERT_TRUE(list_value.get());
ASSERT_EQ(vars.size(), list_value->GetSize());
for (size_t i = 0; i < list_value->GetSize(); ++i) {
const base::Value* value = NULL;
ASSERT_TRUE(list_value->Get(i, &value));
ASSERT_TRUE(Equals(*value, vars[i]));
}
}
}
TEST_F(VarValueConversionsTest, CreateVarVectorFromListValue) {
{
// Test empty list.
base::ListValue list_value;
std::vector<PP_Var> vars;
ASSERT_TRUE(CreateVarVectorFromListValue(list_value, &vars));
ASSERT_EQ(0u, vars.size());
}
{
// Test more complex inputs.
base::ListValue list_value;
scoped_ptr<base::DictionaryValue> dict_value(new base::DictionaryValue());
dict_value->SetString("string_key", "string_value");
scoped_ptr<base::ListValue> sub_list_value(new base::ListValue());
sub_list_value->AppendInteger(2);
sub_list_value->AppendBoolean(true);
list_value.Append(dict_value.release());
list_value.AppendString("string_value");
list_value.Append(sub_list_value.release());
list_value.AppendDouble(1);
list_value.Append(base::Value::CreateNullValue());
std::vector<PP_Var> vars;
ASSERT_TRUE(CreateVarVectorFromListValue(list_value, &vars));
ASSERT_EQ(list_value.GetSize(), vars.size());
for (size_t i = 0; i < list_value.GetSize(); ++i) {
const base::Value* value = NULL;
ASSERT_TRUE(list_value.Get(i, &value));
ASSERT_TRUE(Equals(*value, vars[i]));
PpapiGlobals::Get()->GetVarTracker()->ReleaseVar(vars[i]);
}
}
}
} // namespace ppapi