// Copyright 2013 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "gin/converter.h" #include #include #include #include #include "base/compiler_specific.h" #include "base/strings/utf_string_conversions.h" #include "gin/function_template.h" #include "gin/handle.h" #include "gin/public/isolate_holder.h" #include "gin/test/v8_test.h" #include "gin/wrappable.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" #include "v8/include/v8-container.h" #include "v8/include/v8-forward.h" #include "v8/include/v8-function.h" #include "v8/include/v8-isolate.h" #include "v8/include/v8-primitive.h" #include "v8/include/v8-template.h" namespace gin { using v8::Array; using v8::Boolean; using v8::HandleScope; using v8::Integer; using v8::Local; using v8::Null; using v8::Number; using v8::Object; using v8::String; using v8::Undefined; using v8::Value; typedef V8Test ConverterTest; TEST_F(ConverterTest, Bool) { HandleScope handle_scope(instance_->isolate()); EXPECT_TRUE(Converter::ToV8(instance_->isolate(), true)->StrictEquals( Boolean::New(instance_->isolate(), true))); EXPECT_TRUE(Converter::ToV8(instance_->isolate(), false)->StrictEquals( Boolean::New(instance_->isolate(), false))); struct { Local input; bool expected; } test_data[] = { {Boolean::New(instance_->isolate(), false).As(), false}, {Boolean::New(instance_->isolate(), true).As(), true}, {Number::New(instance_->isolate(), 0).As(), false}, {Number::New(instance_->isolate(), 1).As(), true}, {Number::New(instance_->isolate(), -1).As(), true}, {Number::New(instance_->isolate(), 0.1).As(), true}, {String::NewFromUtf8(instance_->isolate(), "", v8::NewStringType::kNormal) .ToLocalChecked() .As(), false}, {String::NewFromUtf8(instance_->isolate(), "foo", v8::NewStringType::kNormal) .ToLocalChecked() .As(), true}, {Object::New(instance_->isolate()).As(), true}, {Null(instance_->isolate()).As(), false}, {Undefined(instance_->isolate()).As(), false}, }; for (size_t i = 0; i < std::size(test_data); ++i) { bool result = false; EXPECT_TRUE(Converter::FromV8(instance_->isolate(), test_data[i].input, &result)); EXPECT_EQ(test_data[i].expected, result); result = true; EXPECT_TRUE(Converter::FromV8(instance_->isolate(), test_data[i].input, &result)); EXPECT_EQ(test_data[i].expected, result); } } TEST_F(ConverterTest, String16) { v8::Isolate* isolate = instance_->isolate(); HandleScope handle_scope(isolate); EXPECT_TRUE(Converter::ToV8(isolate, u"") ->StrictEquals(StringToV8(isolate, ""))); EXPECT_TRUE(Converter::ToV8(isolate, u"hello") ->StrictEquals(StringToV8(isolate, "hello"))); std::u16string result; ASSERT_FALSE( Converter::FromV8(isolate, v8::False(isolate), &result)); ASSERT_FALSE( Converter::FromV8(isolate, v8::True(isolate), &result)); ASSERT_TRUE(Converter::FromV8( isolate, v8::String::Empty(isolate), &result)); EXPECT_EQ(result, std::u16string()); ASSERT_TRUE(Converter::FromV8( isolate, StringToV8(isolate, "hello"), &result)); EXPECT_EQ(result, u"hello"); } TEST_F(ConverterTest, Int32) { HandleScope handle_scope(instance_->isolate()); int test_data_to[] = {-1, 0, 1}; for (size_t i = 0; i < std::size(test_data_to); ++i) { EXPECT_TRUE(Converter::ToV8(instance_->isolate(), test_data_to[i]) ->StrictEquals( Integer::New(instance_->isolate(), test_data_to[i]))); } struct { v8::Local input; bool expect_success; int expected_result; } test_data_from[] = { {Boolean::New(instance_->isolate(), false).As(), false, 0}, {Boolean::New(instance_->isolate(), true).As(), false, 0}, {Integer::New(instance_->isolate(), -1).As(), true, -1}, {Integer::New(instance_->isolate(), 0).As(), true, 0}, {Integer::New(instance_->isolate(), 1).As(), true, 1}, {Number::New(instance_->isolate(), -1).As(), true, -1}, {Number::New(instance_->isolate(), 1.1).As(), false, 0}, {String::NewFromUtf8(instance_->isolate(), "42", v8::NewStringType::kNormal) .ToLocalChecked() .As(), false, 0}, {String::NewFromUtf8(instance_->isolate(), "foo", v8::NewStringType::kNormal) .ToLocalChecked() .As(), false, 0}, {Object::New(instance_->isolate()).As(), false, 0}, {Array::New(instance_->isolate()).As(), false, 0}, {v8::Null(instance_->isolate()).As(), false, 0}, {v8::Undefined(instance_->isolate()).As(), false, 0}, }; for (size_t i = 0; i < std::size(test_data_from); ++i) { int32_t result = std::numeric_limits::min(); bool success = Converter::FromV8(instance_->isolate(), test_data_from[i].input, &result); EXPECT_EQ(test_data_from[i].expect_success, success) << i; if (success) EXPECT_EQ(test_data_from[i].expected_result, result) << i; } } TEST_F(ConverterTest, Vector) { HandleScope handle_scope(instance_->isolate()); std::vector expected; expected.push_back(-1); expected.push_back(0); expected.push_back(1); auto js_array = Converter>::ToV8(instance_->isolate(), expected) .As(); EXPECT_EQ(3u, js_array->Length()); v8::Local context = instance_->isolate()->GetCurrentContext(); for (size_t i = 0; i < expected.size(); ++i) { EXPECT_TRUE( Integer::New(instance_->isolate(), expected[i]) ->StrictEquals( js_array->Get(context, static_cast(i)).ToLocalChecked())); } } TEST_F(ConverterTest, VectorOfVectors) { HandleScope handle_scope(instance_->isolate()); std::vector> vector_of_vectors = { {1, 2, 3}, {4, 5, 6}, }; v8::Local v8_value = ConvertToV8(instance_->isolate(), vector_of_vectors); std::vector> out_value; ASSERT_TRUE(ConvertFromV8(instance_->isolate(), v8_value, &out_value)); EXPECT_THAT(out_value, testing::ContainerEq(vector_of_vectors)); } namespace { class MyObject : public Wrappable { public: static WrapperInfo kWrapperInfo; static gin::Handle Create(v8::Isolate* isolate) { return CreateHandle(isolate, new MyObject()); } }; WrapperInfo MyObject::kWrapperInfo = {kEmbedderNativeGin}; } // namespace TEST_F(ConverterTest, VectorOfWrappables) { v8::Isolate* isolate = instance_->isolate(); v8::HandleScope handle_scope(isolate); Handle obj = MyObject::Create(isolate); std::vector vector = {obj.get()}; v8::MaybeLocal maybe = ConvertToV8(isolate, vector); v8::Local array; ASSERT_TRUE(maybe.ToLocal(&array)); v8::Local array2; ASSERT_TRUE(TryConvertToV8(isolate, vector, &array2)); std::vector out_value; ASSERT_TRUE(ConvertFromV8(isolate, array, &out_value)); EXPECT_THAT(out_value, testing::ContainerEq(vector)); std::vector out_value2; ASSERT_TRUE(ConvertFromV8(isolate, array2, &out_value2)); EXPECT_THAT(out_value2, testing::ContainerEq(vector)); } namespace { class MoveOnlyObject { public: MoveOnlyObject() = default; MoveOnlyObject(const MoveOnlyObject&) = delete; MoveOnlyObject& operator=(const MoveOnlyObject&) = delete; MoveOnlyObject(MoveOnlyObject&&) noexcept = default; MoveOnlyObject& operator=(MoveOnlyObject&&) noexcept = default; }; } // namespace template <> struct Converter { static v8::Local ToV8(v8::Isolate* isolate, MoveOnlyObject in) { return v8::Undefined(isolate); } static bool FromV8(v8::Isolate* isolate, v8::Local val, MoveOnlyObject* out) { *out = MoveOnlyObject(); return true; } }; TEST_F(ConverterTest, MoveOnlyParameters) { v8::Isolate* isolate = instance_->isolate(); v8::HandleScope handle_scope(isolate); auto receives_move_only_obj = [](MoveOnlyObject obj) {}; auto func_templ = gin::CreateFunctionTemplate( isolate, base::BindRepeating(receives_move_only_obj)); v8::Local context = instance_->isolate()->GetCurrentContext(); auto func = func_templ->GetFunction(context).ToLocalChecked(); v8::Local argv[] = {v8::Undefined(isolate)}; func->Call(context, v8::Undefined(isolate), 1, argv).ToLocalChecked(); } } // namespace gin