// 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 #include #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(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(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(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 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 dict_var(new DictionaryVar()); ScopedPPVar var_1(ScopedPPVar::PassRef(), dict_var->GetPPVar()); scoped_refptr 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 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 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 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 dict_var_1(new DictionaryVar()); ScopedPPVar dict_pp_var_1(ScopedPPVar::PassRef(), dict_var_1->GetPPVar()); scoped_refptr dict_var_2(new DictionaryVar()); ScopedPPVar dict_pp_var_2(ScopedPPVar::PassRef(), dict_var_2->GetPPVar()); scoped_refptr 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 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 dict_var_1(new DictionaryVar()); ScopedPPVar dict_pp_var_1(ScopedPPVar::PassRef(), dict_var_1->GetPPVar()); scoped_refptr dict_var_2(new DictionaryVar()); ScopedPPVar dict_pp_var_2(ScopedPPVar::PassRef(), dict_var_2->GetPPVar()); scoped_refptr array_var(new ArrayVar()); ScopedPPVar array_pp_var(ScopedPPVar::PassRef(), array_var->GetPPVar()); scoped_refptr 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 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 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 list_value( CreateListValueFromVarVector(std::vector())); ASSERT_TRUE(list_value.get()); ASSERT_EQ(0u, list_value->GetSize()); } { // Test more complex inputs. scoped_refptr string_var(new StringVar("string_value")); ScopedPPVar string_pp_var(ScopedPPVar::PassRef(), string_var->GetPPVar()); scoped_refptr 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 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 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 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 vars; ASSERT_TRUE(CreateVarVectorFromListValue(list_value, &vars)); ASSERT_EQ(0u, vars.size()); } { // Test more complex inputs. base::ListValue list_value; scoped_ptr dict_value(new base::DictionaryValue()); dict_value->SetString("string_key", "string_value"); scoped_ptr 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 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