blob: 7ea955c253d566c65f7087b75ebb60dca1aeac97 [file] [log] [blame]
// Copyright 2017 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 "third_party/blink/renderer/bindings/core/v8/native_value_traits_impl.h"
#include <utility>
#include "testing/gtest/include/gtest/gtest-death-test.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/renderer/bindings/core/v8/idl_types.h"
#include "third_party/blink/renderer/bindings/core/v8/to_v8_for_core.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_testing.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_internals.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_test_sequence_callback.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/wtf/vector.h"
namespace blink {
namespace {
v8::Local<v8::Object> EvaluateScriptForObject(V8TestingScope& scope,
const char* source) {
v8::Local<v8::Script> script =
v8::Script::Compile(scope.GetContext(),
V8String(scope.GetIsolate(), source))
.ToLocalChecked();
return script->Run(scope.GetContext()).ToLocalChecked().As<v8::Object>();
}
v8::Local<v8::Array> EvaluateScriptForArray(V8TestingScope& scope,
const char* source) {
return EvaluateScriptForObject(scope, source).As<v8::Array>();
}
TEST(NativeValueTraitsImplTest, IDLInterface) {
V8TestingScope scope;
DummyExceptionStateForTesting exception_state;
Internals* internals = NativeValueTraits<Internals>::NativeValue(
scope.GetIsolate(), v8::Number::New(scope.GetIsolate(), 42),
exception_state);
EXPECT_TRUE(exception_state.HadException());
EXPECT_EQ("Failed to convert value to 'Internals'.",
exception_state.Message());
EXPECT_EQ(nullptr, internals);
}
TEST(NativeValueTraitsImplTest, IDLCallbackFunction) {
V8TestingScope scope;
DummyExceptionStateForTesting exception_state;
v8::Local<v8::Function> function =
v8::Function::New(scope.GetContext(), nullptr).ToLocalChecked();
ASSERT_DEATH_IF_SUPPORTED(
NativeValueTraits<V8TestSequenceCallback>::NativeValue(
scope.GetIsolate(), function, exception_state),
"");
}
TEST(NativeValueTraitsImplTest, IDLRecord) {
V8TestingScope scope;
{
v8::Local<v8::Object> v8_object = v8::Object::New(scope.GetIsolate());
NonThrowableExceptionState exception_state;
const auto& record =
NativeValueTraits<IDLRecord<IDLString, IDLOctet>>::NativeValue(
scope.GetIsolate(), v8_object, exception_state);
EXPECT_TRUE(record.IsEmpty());
}
{
v8::Local<v8::Object> v8_object =
EvaluateScriptForObject(scope, "({ foo: 42, bar: -1024 })");
NonThrowableExceptionState exception_state;
const auto& record =
NativeValueTraits<IDLRecord<IDLByteString, IDLLong>>::NativeValue(
scope.GetIsolate(), v8_object, exception_state);
EXPECT_EQ(2U, record.size());
EXPECT_EQ(std::make_pair(String("foo"), int32_t(42)), record[0]);
EXPECT_EQ(std::make_pair(String("bar"), int32_t(-1024)), record[1]);
}
{
v8::Local<v8::Object> v8_object =
EvaluateScriptForObject(scope,
"Object.defineProperties({}, {"
" foo: {value: 34, enumerable: true},"
" bar: {value: -1024, enumerable: false},"
" baz: {value: 42, enumerable: true},"
"})");
NonThrowableExceptionState exception_state;
const auto& record =
NativeValueTraits<IDLRecord<IDLByteString, IDLLong>>::NativeValue(
scope.GetIsolate(), v8_object, exception_state);
EXPECT_EQ(2U, record.size());
EXPECT_EQ(std::make_pair(String("foo"), int32_t(34)), record[0]);
EXPECT_EQ(std::make_pair(String("baz"), int32_t(42)), record[1]);
}
{
// Exceptions are being thrown in this test, so we need another scope.
V8TestingScope scope;
v8::Local<v8::Object> original_object = EvaluateScriptForObject(
scope, "(self.originalObject = {foo: 34, bar: 42})");
NonThrowableExceptionState exception_state;
const auto& record =
NativeValueTraits<IDLRecord<IDLString, IDLLong>>::NativeValue(
scope.GetIsolate(), original_object, exception_state);
EXPECT_EQ(2U, record.size());
v8::Local<v8::Proxy> proxy =
EvaluateScriptForObject(scope,
"new Proxy(self.originalObject, {"
" getOwnPropertyDescriptor() {"
" return {"
" configurable: true,"
" get enumerable() { throw 'bogus!'; },"
" };"
" }"
"})")
.As<v8::Proxy>();
ExceptionState exception_state_from_proxy(
scope.GetIsolate(), ExceptionState::kExecutionContext,
"NativeValueTraitsImplTest", "IDLRecordTest");
const auto& record_from_proxy =
NativeValueTraits<IDLRecord<IDLString, IDLLong>>::NativeValue(
scope.GetIsolate(), proxy, exception_state_from_proxy);
EXPECT_EQ(0U, record_from_proxy.size());
EXPECT_TRUE(exception_state_from_proxy.HadException());
EXPECT_TRUE(exception_state_from_proxy.Message().IsEmpty());
v8::Local<v8::Value> v8_exception =
exception_state_from_proxy.GetException();
EXPECT_TRUE(v8_exception->IsString());
EXPECT_TRUE(
V8String(scope.GetIsolate(), "bogus!")
->Equals(
scope.GetContext(),
v8_exception->ToString(scope.GetContext()).ToLocalChecked())
.ToChecked());
}
{
v8::Local<v8::Object> v8_object = EvaluateScriptForObject(
scope, "({foo: 42, bar: 0, xx: true, abcd: false})");
NonThrowableExceptionState exception_state;
const auto& record =
NativeValueTraits<IDLRecord<IDLByteString, IDLBoolean>>::NativeValue(
scope.GetIsolate(), v8_object, exception_state);
EXPECT_EQ(4U, record.size());
EXPECT_EQ(std::make_pair(String("foo"), true), record[0]);
EXPECT_EQ(std::make_pair(String("bar"), false), record[1]);
EXPECT_EQ(std::make_pair(String("xx"), true), record[2]);
EXPECT_EQ(std::make_pair(String("abcd"), false), record[3]);
}
{
v8::Local<v8::Array> v8_string_array = EvaluateScriptForArray(
scope, "Object.assign(['Hello, World!', 'Hi, Mom!'], {foo: 'Ohai'})");
NonThrowableExceptionState exception_state;
const auto& record =
NativeValueTraits<IDLRecord<IDLUSVString, IDLString>>::NativeValue(
scope.GetIsolate(), v8_string_array, exception_state);
EXPECT_EQ(3U, record.size());
EXPECT_EQ(std::make_pair(String("0"), String("Hello, World!")), record[0]);
EXPECT_EQ(std::make_pair(String("1"), String("Hi, Mom!")), record[1]);
EXPECT_EQ(std::make_pair(String("foo"), String("Ohai")), record[2]);
}
{
v8::Local<v8::Object> v8_object =
EvaluateScriptForObject(scope, "({[Symbol.toStringTag]: 34, foo: 42})");
// The presence of symbols should throw a TypeError when the conversion to
// the record's key type is attempted.
DummyExceptionStateForTesting exception_state;
const auto& record =
NativeValueTraits<IDLRecord<IDLString, IDLShort>>::NativeValue(
scope.GetIsolate(), v8_object, exception_state);
EXPECT_TRUE(record.IsEmpty());
EXPECT_TRUE(exception_state.HadException());
EXPECT_TRUE(exception_state.Message().IsEmpty());
}
{
v8::Local<v8::Object> v8_object =
EvaluateScriptForObject(scope, "Object.create({foo: 34, bar: 512})");
NonThrowableExceptionState exception_state;
auto record =
NativeValueTraits<IDLRecord<IDLString, IDLUnsignedLong>>::NativeValue(
scope.GetIsolate(), v8_object, exception_state);
EXPECT_TRUE(record.IsEmpty());
v8_object =
EvaluateScriptForObject(scope,
"Object.assign("
" Object.create({foo: 34, bar: 512}),"
" {quux: 42, foo: 1024})");
record =
NativeValueTraits<IDLRecord<IDLString, IDLUnsignedLong>>::NativeValue(
scope.GetIsolate(), v8_object, exception_state);
EXPECT_EQ(2U, record.size());
EXPECT_EQ(std::make_pair(String("quux"), uint32_t(42)), record[0]);
EXPECT_EQ(std::make_pair(String("foo"), uint32_t(1024)), record[1]);
}
{
v8::Local<v8::Object> v8_object = EvaluateScriptForObject(
scope, "({foo: ['Hello, World!', 'Hi, Mom!']})");
NonThrowableExceptionState exception_state;
const auto& record =
NativeValueTraits<IDLRecord<IDLString, IDLSequence<IDLString>>>::
NativeValue(scope.GetIsolate(), v8_object, exception_state);
EXPECT_EQ(1U, record.size());
EXPECT_EQ("foo", record[0].first);
EXPECT_EQ("Hello, World!", record[0].second[0]);
EXPECT_EQ("Hi, Mom!", record[0].second[1]);
}
}
TEST(NativeValueTraitsImplTest, IDLSequence) {
V8TestingScope scope;
{
v8::Local<v8::Array> v8_array = v8::Array::New(scope.GetIsolate());
NonThrowableExceptionState exception_state;
const auto& sequence =
NativeValueTraits<IDLSequence<IDLOctet>>::NativeValue(
scope.GetIsolate(), v8_array, exception_state);
EXPECT_TRUE(sequence.IsEmpty());
}
{
v8::Local<v8::Array> v8_array =
EvaluateScriptForArray(scope, "[0, 1, 2, 3, 4]");
NonThrowableExceptionState exception_state;
const auto& sequence = NativeValueTraits<IDLSequence<IDLLong>>::NativeValue(
scope.GetIsolate(), v8_array, exception_state);
EXPECT_EQ(Vector<int32_t>({0, 1, 2, 3, 4}), sequence);
}
{
const double double_pi = 3.141592653589793238;
const float float_pi = double_pi;
v8::Local<v8::Array> v8_real_array =
EvaluateScriptForArray(scope, "[3.141592653589793238]");
NonThrowableExceptionState exception_state;
Vector<double> double_vector =
NativeValueTraits<IDLSequence<IDLDouble>>::NativeValue(
scope.GetIsolate(), v8_real_array, exception_state);
EXPECT_EQ(1U, double_vector.size());
EXPECT_EQ(double_pi, double_vector[0]);
Vector<float> float_vector =
NativeValueTraits<IDLSequence<IDLFloat>>::NativeValue(
scope.GetIsolate(), v8_real_array, exception_state);
EXPECT_EQ(1U, float_vector.size());
EXPECT_EQ(float_pi, float_vector[0]);
}
{
v8::Local<v8::Array> v8_array =
EvaluateScriptForArray(scope, "['Vini, vidi, vici.', 65535, 0.125]");
NonThrowableExceptionState exception_state;
HeapVector<ScriptValue> script_value_vector =
NativeValueTraits<IDLSequence<ScriptValue>>::NativeValue(
scope.GetIsolate(), v8_array, exception_state);
EXPECT_EQ(3U, script_value_vector.size());
String report_on_zela;
EXPECT_TRUE(script_value_vector[0].ToString(report_on_zela));
EXPECT_EQ("Vini, vidi, vici.", report_on_zela);
EXPECT_EQ(65535U,
ToUInt32(scope.GetIsolate(), script_value_vector[1].V8Value(),
kNormalConversion, exception_state));
}
{
v8::Local<v8::Array> v8_string_array_array =
EvaluateScriptForArray(scope, "[['foo', 'bar'], ['x', 'y', 'z']]");
NonThrowableExceptionState exception_state;
Vector<Vector<String>> string_vector_vector =
NativeValueTraits<IDLSequence<IDLSequence<IDLString>>>::NativeValue(
scope.GetIsolate(), v8_string_array_array, exception_state);
EXPECT_EQ(2U, string_vector_vector.size());
EXPECT_EQ(2U, string_vector_vector[0].size());
EXPECT_EQ("foo", string_vector_vector[0][0]);
EXPECT_EQ("bar", string_vector_vector[0][1]);
EXPECT_EQ(3U, string_vector_vector[1].size());
EXPECT_EQ("x", string_vector_vector[1][0]);
EXPECT_EQ("y", string_vector_vector[1][1]);
EXPECT_EQ("z", string_vector_vector[1][2]);
}
{
v8::Local<v8::Array> v8_array =
EvaluateScriptForArray(scope,
"let arr = [1, 2, 3];"
"let iterations = ["
" {done: false, value: 8},"
" {done: false, value: 5},"
" {done: true}"
"];"
"arr[Symbol.iterator] = function() {"
" let i = 0;"
" return {next: () => iterations[i++]};"
"}; arr");
NonThrowableExceptionState exception_state;
const auto& sequence = NativeValueTraits<IDLSequence<IDLByte>>::NativeValue(
scope.GetIsolate(), v8_array, exception_state);
EXPECT_EQ(Vector<int8_t>({1, 2, 3}), sequence);
}
{
v8::Local<v8::Object> v8_object =
EvaluateScriptForObject(scope,
"let obj = {"
" iterations: ["
" {done: false, value: 55},"
" {done: false, value: 0},"
" {done: true, value: 99}"
" ],"
" [Symbol.iterator]() {"
" let i = 0;"
" return {next: () => this.iterations[i++]};"
" }"
"}; obj");
NonThrowableExceptionState exception_state;
const auto& byte_sequence =
NativeValueTraits<IDLSequence<IDLByte>>::NativeValue(
scope.GetIsolate(), v8_object, exception_state);
EXPECT_EQ(Vector<int8_t>({55, 0}), byte_sequence);
const auto& boolean_sequence =
NativeValueTraits<IDLSequence<IDLBoolean>>::NativeValue(
scope.GetIsolate(), v8_object, exception_state);
EXPECT_EQ(Vector<bool>({true, false}), boolean_sequence);
}
}
} // namespace
} // namespace blink