| // 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. |
| |
| #ifdef UNSAFE_BUFFERS_BUILD |
| // TODO(crbug.com/351564777): Remove this and convert code to safer constructs. |
| #pragma allow_unsafe_buffers |
| #endif |
| |
| #include "gin/converter.h" |
| |
| #include <limits.h> |
| #include <stddef.h> |
| #include <stdint.h> |
| |
| #include <string> |
| |
| #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<bool>::ToV8(instance_->isolate(), true)->StrictEquals( |
| Boolean::New(instance_->isolate(), true))); |
| EXPECT_TRUE(Converter<bool>::ToV8(instance_->isolate(), false)->StrictEquals( |
| Boolean::New(instance_->isolate(), false))); |
| |
| struct { |
| Local<Value> input; |
| bool expected; |
| } test_data[] = { |
| {Boolean::New(instance_->isolate(), false).As<Value>(), false}, |
| {Boolean::New(instance_->isolate(), true).As<Value>(), true}, |
| {Number::New(instance_->isolate(), 0).As<Value>(), false}, |
| {Number::New(instance_->isolate(), 1).As<Value>(), true}, |
| {Number::New(instance_->isolate(), -1).As<Value>(), true}, |
| {Number::New(instance_->isolate(), 0.1).As<Value>(), true}, |
| {String::NewFromUtf8(instance_->isolate(), "", v8::NewStringType::kNormal) |
| .ToLocalChecked() |
| .As<Value>(), |
| false}, |
| {String::NewFromUtf8(instance_->isolate(), "foo", |
| v8::NewStringType::kNormal) |
| .ToLocalChecked() |
| .As<Value>(), |
| true}, |
| {Object::New(instance_->isolate()).As<Value>(), true}, |
| {Null(instance_->isolate()).As<Value>(), false}, |
| {Undefined(instance_->isolate()).As<Value>(), false}, |
| }; |
| |
| for (size_t i = 0; i < std::size(test_data); ++i) { |
| bool result = false; |
| EXPECT_TRUE(Converter<bool>::FromV8(instance_->isolate(), |
| test_data[i].input, &result)); |
| EXPECT_EQ(test_data[i].expected, result); |
| |
| result = true; |
| EXPECT_TRUE(Converter<bool>::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<std::u16string>::ToV8(isolate, u"") |
| ->StrictEquals(StringToV8(isolate, ""))); |
| EXPECT_TRUE(Converter<std::u16string>::ToV8(isolate, u"hello") |
| ->StrictEquals(StringToV8(isolate, "hello"))); |
| |
| std::u16string result; |
| |
| ASSERT_FALSE( |
| Converter<std::u16string>::FromV8(isolate, v8::False(isolate), &result)); |
| ASSERT_FALSE( |
| Converter<std::u16string>::FromV8(isolate, v8::True(isolate), &result)); |
| ASSERT_TRUE(Converter<std::u16string>::FromV8( |
| isolate, v8::String::Empty(isolate), &result)); |
| EXPECT_EQ(result, std::u16string()); |
| ASSERT_TRUE(Converter<std::u16string>::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<int32_t>::ToV8(instance_->isolate(), test_data_to[i]) |
| ->StrictEquals( |
| Integer::New(instance_->isolate(), test_data_to[i]))); |
| } |
| |
| struct { |
| v8::Local<v8::Value> input; |
| bool expect_success; |
| int expected_result; |
| } test_data_from[] = { |
| {Boolean::New(instance_->isolate(), false).As<Value>(), false, 0}, |
| {Boolean::New(instance_->isolate(), true).As<Value>(), false, 0}, |
| {Integer::New(instance_->isolate(), -1).As<Value>(), true, -1}, |
| {Integer::New(instance_->isolate(), 0).As<Value>(), true, 0}, |
| {Integer::New(instance_->isolate(), 1).As<Value>(), true, 1}, |
| {Number::New(instance_->isolate(), -1).As<Value>(), true, -1}, |
| {Number::New(instance_->isolate(), 1.1).As<Value>(), false, 0}, |
| {String::NewFromUtf8(instance_->isolate(), "42", |
| v8::NewStringType::kNormal) |
| .ToLocalChecked() |
| .As<Value>(), |
| false, 0}, |
| {String::NewFromUtf8(instance_->isolate(), "foo", |
| v8::NewStringType::kNormal) |
| .ToLocalChecked() |
| .As<Value>(), |
| false, 0}, |
| {Object::New(instance_->isolate()).As<Value>(), false, 0}, |
| {Array::New(instance_->isolate()).As<Value>(), false, 0}, |
| {v8::Null(instance_->isolate()).As<Value>(), false, 0}, |
| {v8::Undefined(instance_->isolate()).As<Value>(), false, 0}, |
| }; |
| |
| for (size_t i = 0; i < std::size(test_data_from); ++i) { |
| int32_t result = std::numeric_limits<int32_t>::min(); |
| bool success = Converter<int32_t>::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<int> expected; |
| expected.push_back(-1); |
| expected.push_back(0); |
| expected.push_back(1); |
| |
| auto js_array = |
| Converter<std::vector<int>>::ToV8(instance_->isolate(), expected) |
| .As<Array>(); |
| EXPECT_EQ(3u, js_array->Length()); |
| v8::Local<v8::Context> 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<int>(i)).ToLocalChecked())); |
| } |
| } |
| |
| TEST_F(ConverterTest, VectorOfVectors) { |
| HandleScope handle_scope(instance_->isolate()); |
| |
| std::vector<std::vector<int>> vector_of_vectors = { |
| {1, 2, 3}, {4, 5, 6}, |
| }; |
| |
| v8::Local<v8::Value> v8_value = |
| ConvertToV8(instance_->isolate(), vector_of_vectors); |
| std::vector<std::vector<int>> 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<MyObject> { |
| public: |
| static WrapperInfo kWrapperInfo; |
| |
| static gin::Handle<MyObject> 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<MyObject> obj = MyObject::Create(isolate); |
| std::vector<MyObject*> vector = {obj.get()}; |
| v8::MaybeLocal<v8::Value> maybe = ConvertToV8(isolate, vector); |
| v8::Local<v8::Value> array; |
| ASSERT_TRUE(maybe.ToLocal(&array)); |
| v8::Local<v8::Value> array2; |
| ASSERT_TRUE(TryConvertToV8(isolate, vector, &array2)); |
| |
| std::vector<MyObject*> out_value; |
| ASSERT_TRUE(ConvertFromV8(isolate, array, &out_value)); |
| EXPECT_THAT(out_value, testing::ContainerEq(vector)); |
| std::vector<MyObject*> 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<MoveOnlyObject> { |
| static v8::Local<v8::Value> ToV8(v8::Isolate* isolate, MoveOnlyObject in) { |
| return v8::Undefined(isolate); |
| } |
| static bool FromV8(v8::Isolate* isolate, |
| v8::Local<v8::Value> 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<v8::Context> context = instance_->isolate()->GetCurrentContext(); |
| auto func = func_templ->GetFunction(context).ToLocalChecked(); |
| v8::Local<v8::Value> argv[] = {v8::Undefined(isolate)}; |
| func->Call(context, v8::Undefined(isolate), 1, argv).ToLocalChecked(); |
| } |
| |
| TEST_F(ConverterTest, VectorOfMoveOnly) { |
| v8::Isolate* isolate = instance_->isolate(); |
| v8::HandleScope handle_scope(isolate); |
| |
| v8::Local<v8::Value> v8_value = v8::Array::New(instance_->isolate(), 1); |
| std::vector<MoveOnlyObject> out_value; |
| ASSERT_TRUE(ConvertFromV8(instance_->isolate(), v8_value, &out_value)); |
| EXPECT_EQ(out_value.size(), 1u); |
| } |
| |
| } // namespace gin |