1 // Copyright 2013 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "gin/wrappable.h"
7 #include "base/check.h"
8 #include "gin/arguments.h"
9 #include "gin/handle.h"
10 #include "gin/object_template_builder.h"
11 #include "gin/per_isolate_data.h"
12 #include "gin/public/isolate_holder.h"
13 #include "gin/test/v8_test.h"
14 #include "gin/try_catch.h"
15 #include "testing/gtest/include/gtest/gtest.h"
16 #include "v8/include/v8-function.h"
17 #include "v8/include/v8-message.h"
18 #include "v8/include/v8-script.h"
24 // A non-member function to be bound to an ObjectTemplateBuilder.
25 void NonMemberMethod() {}
27 // This useless base class ensures that the value of a pointer to a MyObject
28 // (below) is not the same as the value of that pointer cast to the object's
29 // WrappableBase base.
32 BaseClass() : value_(23) {}
33 BaseClass(const BaseClass&) = delete;
34 BaseClass& operator=(const BaseClass&) = delete;
35 virtual ~BaseClass() = default;
37 // So the compiler doesn't complain that |value_| is unused.
38 int value() const { return value_; }
44 class MyObject : public BaseClass,
45 public Wrappable<MyObject> {
47 MyObject(const MyObject&) = delete;
48 MyObject& operator=(const MyObject&) = delete;
50 static WrapperInfo kWrapperInfo;
52 static gin::Handle<MyObject> Create(v8::Isolate* isolate) {
53 return CreateHandle(isolate, new MyObject());
56 int value() const { return value_; }
57 void set_value(int value) { value_ = value; }
62 MyObject() : value_(0) {}
63 ObjectTemplateBuilder GetObjectTemplateBuilder(v8::Isolate* isolate) final {
64 return Wrappable<MyObject>::GetObjectTemplateBuilder(isolate)
65 .SetProperty("value", &MyObject::value, &MyObject::set_value)
66 .SetMethod("memberMethod", &MyObject::Method)
67 .SetMethod("nonMemberMethod", &NonMemberMethod);
69 ~MyObject() override = default;
75 class MyObject2 : public Wrappable<MyObject2> {
77 static WrapperInfo kWrapperInfo;
80 class MyNamedObject : public Wrappable<MyNamedObject> {
82 MyNamedObject(const MyNamedObject&) = delete;
83 MyNamedObject& operator=(const MyNamedObject&) = delete;
85 static WrapperInfo kWrapperInfo;
87 static gin::Handle<MyNamedObject> Create(v8::Isolate* isolate) {
88 return CreateHandle(isolate, new MyNamedObject());
94 MyNamedObject() = default;
95 ObjectTemplateBuilder GetObjectTemplateBuilder(v8::Isolate* isolate) final {
96 return Wrappable<MyNamedObject>::GetObjectTemplateBuilder(isolate)
97 .SetMethod("memberMethod", &MyNamedObject::Method)
98 .SetMethod("nonMemberMethod", &NonMemberMethod);
100 const char* GetTypeName() final { return "MyNamedObject"; }
101 ~MyNamedObject() override = default;
104 WrapperInfo MyObject::kWrapperInfo = { kEmbedderNativeGin };
105 WrapperInfo MyObject2::kWrapperInfo = { kEmbedderNativeGin };
106 WrapperInfo MyNamedObject::kWrapperInfo = {kEmbedderNativeGin};
110 typedef V8Test WrappableTest;
112 TEST_F(WrappableTest, WrapAndUnwrap) {
113 v8::Isolate* isolate = instance_->isolate();
114 v8::HandleScope handle_scope(isolate);
116 Handle<MyObject> obj = MyObject::Create(isolate);
118 v8::Local<v8::Value> wrapper =
119 ConvertToV8(isolate, obj.get()).ToLocalChecked();
120 EXPECT_FALSE(wrapper.IsEmpty());
122 MyObject* unwrapped = NULL;
123 EXPECT_TRUE(ConvertFromV8(isolate, wrapper, &unwrapped));
124 EXPECT_EQ(obj.get(), unwrapped);
127 TEST_F(WrappableTest, UnwrapFailures) {
128 v8::Isolate* isolate = instance_->isolate();
129 v8::HandleScope handle_scope(isolate);
131 // Something that isn't an object.
132 v8::Local<v8::Value> thing = v8::Number::New(isolate, 42);
133 MyObject* unwrapped = NULL;
134 EXPECT_FALSE(ConvertFromV8(isolate, thing, &unwrapped));
135 EXPECT_FALSE(unwrapped);
137 // An object that's not wrapping anything.
138 thing = v8::Object::New(isolate);
139 EXPECT_FALSE(ConvertFromV8(isolate, thing, &unwrapped));
140 EXPECT_FALSE(unwrapped);
142 // An object that's wrapping a C++ object of the wrong type.
144 thing = ConvertToV8(isolate, new MyObject2()).ToLocalChecked();
145 EXPECT_FALSE(ConvertFromV8(isolate, thing, &unwrapped));
146 EXPECT_FALSE(unwrapped);
149 TEST_F(WrappableTest, GetAndSetProperty) {
150 v8::Isolate* isolate = instance_->isolate();
151 v8::HandleScope handle_scope(isolate);
153 gin::Handle<MyObject> obj = MyObject::Create(isolate);
156 EXPECT_EQ(42, obj->value());
158 v8::Local<v8::String> source = StringToV8(isolate,
160 " if (obj.value !== 42) throw 'FAIL';"
161 " else obj.value = 191; })");
162 EXPECT_FALSE(source.IsEmpty());
164 gin::TryCatch try_catch(isolate);
165 v8::Local<v8::Script> script =
166 v8::Script::Compile(context_.Get(isolate), source).ToLocalChecked();
167 v8::Local<v8::Value> val =
168 script->Run(context_.Get(isolate)).ToLocalChecked();
169 v8::Local<v8::Function> func;
170 EXPECT_TRUE(ConvertFromV8(isolate, val, &func));
171 v8::Local<v8::Value> argv[] = {
172 ConvertToV8(isolate, obj.get()).ToLocalChecked(),
174 func->Call(context_.Get(isolate), v8::Undefined(isolate), 1, argv)
176 EXPECT_FALSE(try_catch.HasCaught());
177 EXPECT_EQ("", try_catch.GetStackTrace());
179 EXPECT_EQ(191, obj->value());
182 TEST_F(WrappableTest, MethodInvocationErrorsOnUnnamedObject) {
183 v8::Isolate* isolate = instance_->isolate();
184 v8::HandleScope handle_scope(isolate);
185 v8::Local<v8::Context> context = context_.Get(isolate);
187 gin::Handle<MyObject> obj = MyObject::Create(isolate);
189 v8::Local<v8::Object> v8_object =
190 ConvertToV8(isolate, obj.get()).ToLocalChecked().As<v8::Object>();
191 v8::Local<v8::Value> member_method =
192 v8_object->Get(context, StringToV8(isolate, "memberMethod"))
194 ASSERT_TRUE(member_method->IsFunction());
195 v8::Local<v8::Value> non_member_method =
196 v8_object->Get(context, StringToV8(isolate, "nonMemberMethod"))
198 ASSERT_TRUE(non_member_method->IsFunction());
200 auto get_error = [isolate, context](v8::Local<v8::Value> function_to_run,
201 v8::Local<v8::Value> context_object) {
202 constexpr char kScript[] =
203 "(function(toRun, contextObject) { toRun.apply(contextObject, []); })";
204 v8::Local<v8::String> source = StringToV8(isolate, kScript);
205 EXPECT_FALSE(source.IsEmpty());
207 v8::TryCatch try_catch(isolate);
208 v8::Local<v8::Script> script =
209 v8::Script::Compile(context, source).ToLocalChecked();
210 v8::Local<v8::Value> val = script->Run(context).ToLocalChecked();
211 v8::Local<v8::Function> func;
212 EXPECT_TRUE(ConvertFromV8(isolate, val, &func));
213 v8::Local<v8::Value> argv[] = {function_to_run, context_object};
214 func->Call(context, v8::Undefined(isolate), std::size(argv), argv)
215 .FromMaybe(v8::Local<v8::Value>());
216 if (!try_catch.HasCaught())
217 return std::string();
218 return V8ToString(isolate, try_catch.Message()->Get());
221 EXPECT_EQ(std::string(), get_error(member_method, v8_object));
222 EXPECT_EQ(std::string(), get_error(non_member_method, v8_object));
224 EXPECT_EQ("Uncaught TypeError: Illegal invocation",
225 get_error(member_method, v8::Null(isolate)));
226 // A non-member function shouldn't throw errors for being applied on a
227 // null (or invalid) object.
228 EXPECT_EQ(std::string(), get_error(non_member_method, v8::Null(isolate)));
230 v8::Local<v8::Object> wrong_object = v8::Object::New(isolate);
231 // We should get an error for passing the wrong object.
232 EXPECT_EQ("Uncaught TypeError: Illegal invocation",
233 get_error(member_method, wrong_object));
234 // But again, not for a "static" method.
235 EXPECT_EQ(std::string(), get_error(non_member_method, v8::Null(isolate)));
238 TEST_F(WrappableTest, MethodInvocationErrorsOnNamedObject) {
239 v8::Isolate* isolate = instance_->isolate();
240 v8::HandleScope handle_scope(isolate);
241 v8::Local<v8::Context> context = context_.Get(isolate);
243 gin::Handle<MyNamedObject> obj = MyNamedObject::Create(isolate);
245 v8::Local<v8::Object> v8_object =
246 ConvertToV8(isolate, obj.get()).ToLocalChecked().As<v8::Object>();
247 v8::Local<v8::Value> member_method =
248 v8_object->Get(context, StringToV8(isolate, "memberMethod"))
250 ASSERT_TRUE(member_method->IsFunction());
251 v8::Local<v8::Value> non_member_method =
252 v8_object->Get(context, StringToV8(isolate, "nonMemberMethod"))
254 ASSERT_TRUE(non_member_method->IsFunction());
256 auto get_error = [isolate, context](v8::Local<v8::Value> function_to_run,
257 v8::Local<v8::Value> context_object) {
258 constexpr char kScript[] =
259 "(function(toRun, contextObject) { toRun.apply(contextObject, []); })";
260 v8::Local<v8::String> source = StringToV8(isolate, kScript);
261 EXPECT_FALSE(source.IsEmpty());
263 v8::TryCatch try_catch(isolate);
264 v8::Local<v8::Script> script =
265 v8::Script::Compile(context, source).ToLocalChecked();
266 v8::Local<v8::Value> val = script->Run(context).ToLocalChecked();
267 v8::Local<v8::Function> func;
268 EXPECT_TRUE(ConvertFromV8(isolate, val, &func));
269 v8::Local<v8::Value> argv[] = {function_to_run, context_object};
270 func->Call(context, v8::Undefined(isolate), std::size(argv), argv)
271 .FromMaybe(v8::Local<v8::Value>());
272 if (!try_catch.HasCaught())
273 return std::string();
274 return V8ToString(isolate, try_catch.Message()->Get());
277 EXPECT_EQ(std::string(), get_error(member_method, v8_object));
278 EXPECT_EQ(std::string(), get_error(non_member_method, v8_object));
281 "Uncaught TypeError: Illegal invocation: Function must be called on "
282 "an object of type MyNamedObject",
283 get_error(member_method, v8::Null(isolate)));
284 // A non-member function shouldn't throw errors for being applied on a
285 // null (or invalid) object.
286 EXPECT_EQ(std::string(), get_error(non_member_method, v8::Null(isolate)));
288 v8::Local<v8::Object> wrong_object = v8::Object::New(isolate);
289 // We should get an error for passing the wrong object.
291 "Uncaught TypeError: Illegal invocation: Function must be called on "
292 "an object of type MyNamedObject",
293 get_error(member_method, wrong_object));
294 // But again, not for a "static" method.
295 EXPECT_EQ(std::string(), get_error(non_member_method, v8::Null(isolate)));
298 class MyObjectWithLazyProperties
299 : public Wrappable<MyObjectWithLazyProperties> {
301 MyObjectWithLazyProperties(const MyObjectWithLazyProperties&) = delete;
302 MyObjectWithLazyProperties& operator=(const MyObjectWithLazyProperties&) =
305 static WrapperInfo kWrapperInfo;
307 static gin::Handle<MyObjectWithLazyProperties> Create(v8::Isolate* isolate) {
308 return CreateHandle(isolate, new MyObjectWithLazyProperties());
311 int access_count() const { return access_count_; }
314 MyObjectWithLazyProperties() = default;
316 ObjectTemplateBuilder GetObjectTemplateBuilder(v8::Isolate* isolate) final {
317 return Wrappable::GetObjectTemplateBuilder(isolate)
318 .SetLazyDataProperty("fortyTwo", &MyObjectWithLazyProperties::FortyTwo)
319 .SetLazyDataProperty("self",
320 base::BindRepeating([](gin::Arguments* arguments) {
321 v8::Local<v8::Value> holder;
322 CHECK(arguments->GetHolder(&holder));
332 int access_count_ = 0;
335 WrapperInfo MyObjectWithLazyProperties::kWrapperInfo = {kEmbedderNativeGin};
337 TEST_F(WrappableTest, LazyPropertyGetterIsCalledOnce) {
338 v8::Isolate* isolate = instance_->isolate();
339 v8::HandleScope handle_scope(isolate);
340 v8::Local<v8::Context> context = context_.Get(isolate);
342 auto handle = MyObjectWithLazyProperties::Create(isolate);
343 v8::Local<v8::Object> v8_object = handle.ToV8().As<v8::Object>();
344 v8::Local<v8::String> key = StringToSymbol(isolate, "fortyTwo");
345 v8::Local<v8::Value> value;
347 bool has_own_property = false;
348 ASSERT_TRUE(v8_object->HasOwnProperty(context, key).To(&has_own_property));
349 EXPECT_TRUE(has_own_property);
351 EXPECT_EQ(0, handle->access_count());
353 ASSERT_TRUE(v8_object->Get(context, key).ToLocal(&value));
354 EXPECT_TRUE(value->StrictEquals(v8::Int32::New(isolate, 42)));
355 EXPECT_EQ(1, handle->access_count());
357 ASSERT_TRUE(v8_object->Get(context, key).ToLocal(&value));
358 EXPECT_TRUE(value->StrictEquals(v8::Int32::New(isolate, 42)));
359 EXPECT_EQ(1, handle->access_count());
362 TEST_F(WrappableTest, LazyPropertyGetterCanBeSetFirst) {
363 v8::Isolate* isolate = instance_->isolate();
364 v8::HandleScope handle_scope(isolate);
365 v8::Local<v8::Context> context = context_.Get(isolate);
367 auto handle = MyObjectWithLazyProperties::Create(isolate);
368 v8::Local<v8::Object> v8_object = handle.ToV8().As<v8::Object>();
369 v8::Local<v8::String> key = StringToSymbol(isolate, "fortyTwo");
370 v8::Local<v8::Value> value;
372 EXPECT_EQ(0, handle->access_count());
376 v8_object->Set(context, key, v8::Int32::New(isolate, 1701)).To(&set_ok));
378 ASSERT_TRUE(v8_object->Get(context, key).ToLocal(&value));
379 EXPECT_TRUE(value->StrictEquals(v8::Int32::New(isolate, 1701)));
380 EXPECT_EQ(0, handle->access_count());
383 TEST_F(WrappableTest, LazyPropertyGetterCanBindSpecialArguments) {
384 v8::Isolate* isolate = instance_->isolate();
385 v8::HandleScope handle_scope(isolate);
386 v8::Local<v8::Context> context = context_.Get(isolate);
388 auto handle = MyObjectWithLazyProperties::Create(isolate);
389 v8::Local<v8::Object> v8_object = handle.ToV8().As<v8::Object>();
390 v8::Local<v8::Value> value;
392 v8_object->Get(context, StringToSymbol(isolate, "self")).ToLocal(&value));
393 EXPECT_TRUE(v8_object == value);
396 TEST_F(WrappableTest, CannotConstruct) {
397 v8::Isolate* isolate = instance_->isolate();
398 v8::HandleScope handle_scope(isolate);
399 v8::Local<v8::Context> context = context_.Get(isolate);
401 Handle<MyObject> obj = MyObject::Create(isolate);
402 v8::Local<v8::Value> wrapper =
403 ConvertToV8(isolate, obj.get()).ToLocalChecked();
404 ASSERT_FALSE(wrapper.IsEmpty());
406 v8::Local<v8::String> source =
407 StringToV8(isolate, "(obj => new obj.constructor())");
408 v8::Local<v8::Script> script =
409 v8::Script::Compile(context, source).ToLocalChecked();
410 v8::Local<v8::Function> function =
411 script->Run(context).ToLocalChecked().As<v8::Function>();
414 TryCatch try_catch(isolate);
416 ->Call(context, v8::Undefined(isolate), 1,
417 (v8::Local<v8::Value>[]){wrapper})
419 EXPECT_TRUE(try_catch.HasCaught());