1/*
2 * Copyright (C) 2018 Igalia S.L.
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
13 *
14 * You should have received a copy of the GNU Library General Public License
15 * along with this library; see the file COPYING.LIB. If not, write to
16 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18 */
19
20#include "config.h"
21#include "JSCClass.h"
22
23#include "APICast.h"
24#include "JSAPIWrapperGlobalObject.h"
25#include "JSAPIWrapperObject.h"
26#include "JSCCallbackFunction.h"
27#include "JSCClassPrivate.h"
28#include "JSCContextPrivate.h"
29#include "JSCExceptionPrivate.h"
30#include "JSCInlines.h"
31#include "JSCValuePrivate.h"
32#include "JSCallbackObject.h"
33#include "JSRetainPtr.h"
34#include <wtf/glib/GUniquePtr.h>
35#include <wtf/glib/WTFGType.h>
36
37/**
38 * SECTION: JSCClass
39 * @short_description: JavaScript custom class
40 * @title: JSCClass
41 * @see_also: JSCContext
42 *
43 * A JSSClass represents a custom JavaScript class registered by the user in a #JSCContext.
44 * It allows to create new JavaScripts objects whose instances are created by the user using
45 * this API.
46 * It's possible to add constructors, properties and methods for a JSSClass by providing
47 * #GCallback<!-- -->s to implement them.
48 */
49
50enum {
51 PROP_0,
52
53 PROP_CONTEXT,
54 PROP_NAME,
55 PROP_PARENT
56};
57
58typedef struct _JSCClassPrivate {
59 JSCContext* context;
60 CString name;
61 JSClassRef jsClass;
62 JSCClassVTable* vtable;
63 GDestroyNotify destroyFunction;
64 JSCClass* parentClass;
65 JSC::Weak<JSC::JSObject> prototype;
66 HashMap<CString, JSC::Weak<JSC::JSObject>> constructors;
67} JSCClassPrivate;
68
69struct _JSCClass {
70 GObject parent;
71
72 JSCClassPrivate* priv;
73};
74
75struct _JSCClassClass {
76 GObjectClass parent_class;
77};
78
79WEBKIT_DEFINE_TYPE(JSCClass, jsc_class, G_TYPE_OBJECT)
80
81class VTableExceptionHandler {
82public:
83 VTableExceptionHandler(JSCContext* context, JSValueRef* exception)
84 : m_context(context)
85 , m_exception(exception)
86 , m_savedException(exception ? jsc_context_get_exception(m_context) : nullptr)
87 {
88 }
89
90 ~VTableExceptionHandler()
91 {
92 if (!m_exception)
93 return;
94
95 auto* exception = jsc_context_get_exception(m_context);
96 if (m_savedException.get() == exception)
97 return;
98
99 *m_exception = jscExceptionGetJSValue(exception);
100 if (m_savedException)
101 jsc_context_throw_exception(m_context, m_savedException.get());
102 else
103 jsc_context_clear_exception(m_context);
104 }
105
106private:
107 JSCContext* m_context { nullptr };
108 JSValueRef* m_exception { nullptr };
109 GRefPtr<JSCException> m_savedException;
110};
111
112static bool isWrappedObject(JSC::JSObject* jsObject)
113{
114 JSC::ExecState* exec = jsObject->globalObject()->globalExec();
115 if (jsObject->isGlobalObject())
116 return jsObject->inherits<JSC::JSCallbackObject<JSC::JSAPIWrapperGlobalObject>>(exec->vm());
117 return jsObject->inherits<JSC::JSCallbackObject<JSC::JSAPIWrapperObject>>(exec->vm());
118}
119
120static JSClassRef wrappedObjectClass(JSC::JSObject* jsObject)
121{
122 ASSERT(isWrappedObject(jsObject));
123 if (jsObject->isGlobalObject())
124 return JSC::jsCast<JSC::JSCallbackObject<JSC::JSAPIWrapperGlobalObject>*>(jsObject)->classRef();
125 return JSC::jsCast<JSC::JSCallbackObject<JSC::JSAPIWrapperObject>*>(jsObject)->classRef();
126}
127
128static GRefPtr<JSCContext> jscContextForObject(JSC::JSObject* jsObject)
129{
130 ASSERT(isWrappedObject(jsObject));
131 JSC::JSGlobalObject* globalObject = jsObject->globalObject();
132 JSC::ExecState* exec = globalObject->globalExec();
133 if (jsObject->isGlobalObject()) {
134 JSC::VM& vm = globalObject->vm();
135 if (auto* globalScopeExtension = vm.vmEntryGlobalObject(exec)->globalScopeExtension())
136 exec = JSC::JSScope::objectAtScope(globalScopeExtension)->globalObject()->globalExec();
137 }
138 return jscContextGetOrCreate(toGlobalRef(exec));
139}
140
141static JSValueRef getProperty(JSContextRef callerContext, JSObjectRef object, JSStringRef propertyName, JSValueRef* exception)
142{
143 JSC::JSLockHolder locker(toJS(callerContext));
144 auto* jsObject = toJS(object);
145 if (!isWrappedObject(jsObject))
146 return nullptr;
147
148 auto context = jscContextForObject(jsObject);
149 gpointer instance = jscContextWrappedObject(context.get(), object);
150 if (!instance)
151 return nullptr;
152
153 VTableExceptionHandler exceptionHandler(context.get(), exception);
154
155 JSClassRef jsClass = wrappedObjectClass(jsObject);
156 for (auto* jscClass = jscContextGetRegisteredClass(context.get(), jsClass); jscClass; jscClass = jscClass->priv->parentClass) {
157 if (!jscClass->priv->vtable)
158 continue;
159
160 if (auto* getPropertyFunction = jscClass->priv->vtable->get_property) {
161 if (GRefPtr<JSCValue> value = adoptGRef(getPropertyFunction(jscClass, context.get(), instance, propertyName->string().utf8().data())))
162 return jscValueGetJSValue(value.get());
163 }
164 }
165 return nullptr;
166}
167
168static bool setProperty(JSContextRef callerContext, JSObjectRef object, JSStringRef propertyName, JSValueRef value, JSValueRef* exception)
169{
170 JSC::JSLockHolder locker(toJS(callerContext));
171 auto* jsObject = toJS(object);
172 if (!isWrappedObject(jsObject))
173 return false;
174
175 auto context = jscContextForObject(jsObject);
176 gpointer instance = jscContextWrappedObject(context.get(), object);
177 if (!instance)
178 return false;
179
180 VTableExceptionHandler exceptionHandler(context.get(), exception);
181
182 GRefPtr<JSCValue> propertyValue;
183 JSClassRef jsClass = wrappedObjectClass(jsObject);
184 for (auto* jscClass = jscContextGetRegisteredClass(context.get(), jsClass); jscClass; jscClass = jscClass->priv->parentClass) {
185 if (!jscClass->priv->vtable)
186 continue;
187
188 if (auto* setPropertyFunction = jscClass->priv->vtable->set_property) {
189 if (!propertyValue)
190 propertyValue = jscContextGetOrCreateValue(context.get(), value);
191 if (setPropertyFunction(jscClass, context.get(), instance, propertyName->string().utf8().data(), propertyValue.get()))
192 return true;
193 }
194 }
195 return false;
196}
197
198static bool hasProperty(JSContextRef callerContext, JSObjectRef object, JSStringRef propertyName)
199{
200 JSC::JSLockHolder locker(toJS(callerContext));
201 auto* jsObject = toJS(object);
202 if (!isWrappedObject(jsObject))
203 return false;
204
205 auto context = jscContextForObject(jsObject);
206 gpointer instance = jscContextWrappedObject(context.get(), object);
207 if (!instance)
208 return false;
209
210 JSClassRef jsClass = wrappedObjectClass(jsObject);
211 for (auto* jscClass = jscContextGetRegisteredClass(context.get(), jsClass); jscClass; jscClass = jscClass->priv->parentClass) {
212 if (!jscClass->priv->vtable)
213 continue;
214
215 if (auto* hasPropertyFunction = jscClass->priv->vtable->has_property) {
216 if (hasPropertyFunction(jscClass, context.get(), instance, propertyName->string().utf8().data()))
217 return true;
218 }
219 }
220
221 return false;
222}
223
224static bool deleteProperty(JSContextRef callerContext, JSObjectRef object, JSStringRef propertyName, JSValueRef* exception)
225{
226 JSC::JSLockHolder locker(toJS(callerContext));
227 auto* jsObject = toJS(object);
228 if (!isWrappedObject(jsObject))
229 return false;
230
231 auto context = jscContextForObject(jsObject);
232 gpointer instance = jscContextWrappedObject(context.get(), object);
233 if (!instance)
234 return false;
235
236 VTableExceptionHandler exceptionHandler(context.get(), exception);
237
238 JSClassRef jsClass = wrappedObjectClass(jsObject);
239 for (auto* jscClass = jscContextGetRegisteredClass(context.get(), jsClass); jscClass; jscClass = jscClass->priv->parentClass) {
240 if (!jscClass->priv->vtable)
241 continue;
242
243 if (auto* deletePropertyFunction = jscClass->priv->vtable->delete_property) {
244 if (deletePropertyFunction(jscClass, context.get(), instance, propertyName->string().utf8().data()))
245 return true;
246 }
247 }
248 return false;
249}
250
251static void getPropertyNames(JSContextRef callerContext, JSObjectRef object, JSPropertyNameAccumulatorRef propertyNames)
252{
253 JSC::JSLockHolder locker(toJS(callerContext));
254 auto* jsObject = toJS(object);
255 if (!isWrappedObject(jsObject))
256 return;
257
258 auto context = jscContextForObject(jsObject);
259 gpointer instance = jscContextWrappedObject(context.get(), object);
260 if (!instance)
261 return;
262
263 JSClassRef jsClass = wrappedObjectClass(jsObject);
264 for (auto* jscClass = jscContextGetRegisteredClass(context.get(), jsClass); jscClass; jscClass = jscClass->priv->parentClass) {
265 if (!jscClass->priv->vtable)
266 continue;
267
268 if (auto* enumeratePropertiesFunction = jscClass->priv->vtable->enumerate_properties) {
269 GUniquePtr<char*> properties(enumeratePropertiesFunction(jscClass, context.get(), instance));
270 if (properties) {
271 unsigned i = 0;
272 while (const auto* name = properties.get()[i++]) {
273 JSRetainPtr<JSStringRef> propertyName(Adopt, JSStringCreateWithUTF8CString(name));
274 JSPropertyNameAccumulatorAddName(propertyNames, propertyName.get());
275 }
276 }
277 }
278 }
279}
280
281static void jscClassGetProperty(GObject* object, guint propID, GValue* value, GParamSpec* paramSpec)
282{
283 JSCClass* jscClass = JSC_CLASS(object);
284
285 switch (propID) {
286 case PROP_CONTEXT:
287 g_value_set_object(value, jscClass->priv->context);
288 break;
289 case PROP_NAME:
290 g_value_set_string(value, jscClass->priv->name.data());
291 break;
292 case PROP_PARENT:
293 g_value_set_object(value, jscClass->priv->parentClass);
294 break;
295 default:
296 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propID, paramSpec);
297 }
298}
299
300static void jscClassSetProperty(GObject* object, guint propID, const GValue* value, GParamSpec* paramSpec)
301{
302 JSCClass* jscClass = JSC_CLASS(object);
303
304 switch (propID) {
305 case PROP_CONTEXT:
306 jscClass->priv->context = JSC_CONTEXT(g_value_get_object(value));
307 break;
308 case PROP_NAME:
309 jscClass->priv->name = g_value_get_string(value);
310 break;
311 case PROP_PARENT:
312 if (auto* parent = g_value_get_object(value))
313 jscClass->priv->parentClass = JSC_CLASS(parent);
314 break;
315 default:
316 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propID, paramSpec);
317 }
318}
319
320static void jscClassDispose(GObject* object)
321{
322 JSCClass* jscClass = JSC_CLASS(object);
323 if (jscClass->priv->jsClass) {
324 JSClassRelease(jscClass->priv->jsClass);
325 jscClass->priv->jsClass = nullptr;
326 }
327
328 G_OBJECT_CLASS(jsc_class_parent_class)->dispose(object);
329}
330
331static void jsc_class_class_init(JSCClassClass* klass)
332{
333 GObjectClass* objClass = G_OBJECT_CLASS(klass);
334 objClass->dispose = jscClassDispose;
335 objClass->get_property = jscClassGetProperty;
336 objClass->set_property = jscClassSetProperty;
337
338 /**
339 * JSCClass:context:
340 *
341 * The #JSCContext in which the class was registered.
342 */
343 g_object_class_install_property(objClass,
344 PROP_CONTEXT,
345 g_param_spec_object(
346 "context",
347 "JSCContext",
348 "JSC Context",
349 JSC_TYPE_CONTEXT,
350 static_cast<GParamFlags>(WEBKIT_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)));
351
352 /**
353 * JSCClass:name:
354 *
355 * The name of the class.
356 */
357 g_object_class_install_property(objClass,
358 PROP_NAME,
359 g_param_spec_string(
360 "name",
361 "Name",
362 "The class name",
363 nullptr,
364 static_cast<GParamFlags>(WEBKIT_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)));
365
366 /**
367 * JSCClass:parent:
368 *
369 * The parent class or %NULL in case of final classes.
370 */
371 g_object_class_install_property(objClass,
372 PROP_PARENT,
373 g_param_spec_object(
374 "parent",
375 "Partent",
376 "The parent class",
377 JSC_TYPE_CLASS,
378 static_cast<GParamFlags>(WEBKIT_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)));
379}
380
381/**
382 * JSCClassGetPropertyFunction:
383 * @jsc_class: a #JSCClass
384 * @context: a #JSCContext
385 * @instance: the @jsc_class instance
386 * @name: the property name
387 *
388 * The type of get_property in #JSCClassVTable. This is only required when you need to handle
389 * external properties not added to the prototype.
390 *
391 * Returns: (transfer full) (nullable): a #JSCValue or %NULL to forward the request to
392 * the parent class or prototype chain
393 */
394
395/**
396 * JSCClassSetPropertyFunction:
397 * @jsc_class: a #JSCClass
398 * @context: a #JSCContext
399 * @instance: the @jsc_class instance
400 * @name: the property name
401 * @value: the #JSCValue to set
402 *
403 * The type of set_property in #JSCClassVTable. This is only required when you need to handle
404 * external properties not added to the prototype.
405 *
406 * Returns: %TRUE if handled or %FALSE to forward the request to the parent class or prototype chain.
407 */
408
409/**
410 * JSCClassHasPropertyFunction:
411 * @jsc_class: a #JSCClass
412 * @context: a #JSCContext
413 * @instance: the @jsc_class instance
414 * @name: the property name
415 *
416 * The type of has_property in #JSCClassVTable. This is only required when you need to handle
417 * external properties not added to the prototype.
418 *
419 * Returns: %TRUE if @instance has a property with @name or %FALSE to forward the request
420 * to the parent class or prototype chain.
421 */
422
423/**
424 * JSCClassDeletePropertyFunction:
425 * @jsc_class: a #JSCClass
426 * @context: a #JSCContext
427 * @instance: the @jsc_class instance
428 * @name: the property name
429 *
430 * The type of delete_property in #JSCClassVTable. This is only required when you need to handle
431 * external properties not added to the prototype.
432 *
433 * Returns: %TRUE if handled or %FALSE to to forward the request to the parent class or prototype chain.
434 */
435
436/**
437 * JSCClassEnumeratePropertiesFunction:
438 * @jsc_class: a #JSCClass
439 * @context: a #JSCContext
440 * @instance: the @jsc_class instance
441 *
442 * The type of enumerate_properties in #JSCClassVTable. This is only required when you need to handle
443 * external properties not added to the prototype.
444 *
445 * Returns: (array zero-terminated=1) (transfer full) (nullable): a %NULL-terminated array of strings
446 * containing the property names, or %NULL if @instance doesn't have enumerable properties.
447 */
448
449/**
450 * JSCClassVTable:
451 * @get_property: a #JSCClassGetPropertyFunction for getting a property.
452 * @set_property: a #JSCClassSetPropertyFunction for setting a property.
453 * @has_property: a #JSCClassHasPropertyFunction for querying a property.
454 * @delete_property: a #JSCClassDeletePropertyFunction for deleting a property.
455 * @enumerate_properties: a #JSCClassEnumeratePropertiesFunction for enumerating properties.
456 *
457 * Virtual table for a JSCClass. This can be optionally used when registering a #JSCClass in a #JSCContext
458 * to provide a custom implementation for the class. All virtual functions are optional and can be set to
459 * %NULL to fallback to the default implementation.
460 */
461
462GRefPtr<JSCClass> jscClassCreate(JSCContext* context, const char* name, JSCClass* parentClass, JSCClassVTable* vtable, GDestroyNotify destroyFunction)
463{
464 GRefPtr<JSCClass> jscClass = adoptGRef(JSC_CLASS(g_object_new(JSC_TYPE_CLASS, "context", context, "name", name, "parent", parentClass, nullptr)));
465
466 JSCClassPrivate* priv = jscClass->priv;
467 priv->vtable = vtable;
468 priv->destroyFunction = destroyFunction;
469
470 JSClassDefinition definition = kJSClassDefinitionEmpty;
471 definition.className = priv->name.data();
472
473#define SET_IMPL_IF_NEEDED(definitionFunc, vtableFunc) \
474 for (auto* klass = jscClass.get(); klass; klass = klass->priv->parentClass) { \
475 if (klass->priv->vtable && klass->priv->vtable->vtableFunc) { \
476 definition.definitionFunc = definitionFunc; \
477 break; \
478 } \
479 }
480
481 SET_IMPL_IF_NEEDED(getProperty, get_property);
482 SET_IMPL_IF_NEEDED(setProperty, set_property);
483 SET_IMPL_IF_NEEDED(hasProperty, has_property);
484 SET_IMPL_IF_NEEDED(deleteProperty, delete_property);
485 SET_IMPL_IF_NEEDED(getPropertyNames, enumerate_properties);
486
487#undef SET_IMPL_IF_NEEDED
488
489 priv->jsClass = JSClassCreate(&definition);
490
491 GUniquePtr<char> prototypeName(g_strdup_printf("%sPrototype", priv->name.data()));
492 JSClassDefinition prototypeDefinition = kJSClassDefinitionEmpty;
493 prototypeDefinition.className = prototypeName.get();
494 JSClassRef prototypeClass = JSClassCreate(&prototypeDefinition);
495 priv->prototype = jscContextGetOrCreateJSWrapper(priv->context, prototypeClass);
496 JSClassRelease(prototypeClass);
497
498 if (priv->parentClass)
499 JSObjectSetPrototype(jscContextGetJSContext(priv->context), toRef(priv->prototype.get()), toRef(priv->parentClass->priv->prototype.get()));
500 return jscClass;
501}
502
503JSClassRef jscClassGetJSClass(JSCClass* jscClass)
504{
505 return jscClass->priv->jsClass;
506}
507
508JSC::JSObject* jscClassGetOrCreateJSWrapper(JSCClass* jscClass, gpointer wrappedObject)
509{
510 JSCClassPrivate* priv = jscClass->priv;
511 return jscContextGetOrCreateJSWrapper(priv->context, priv->jsClass, toRef(priv->prototype.get()), wrappedObject, priv->destroyFunction);
512}
513
514JSGlobalContextRef jscClassCreateContextWithJSWrapper(JSCClass* jscClass, gpointer wrappedObject)
515{
516 JSCClassPrivate* priv = jscClass->priv;
517 return jscContextCreateContextWithJSWrapper(priv->context, priv->jsClass, toRef(priv->prototype.get()), wrappedObject, priv->destroyFunction);
518}
519
520void jscClassInvalidate(JSCClass* jscClass)
521{
522 jscClass->priv->context = nullptr;
523}
524
525/**
526 * jsc_class_get_name:
527 * @jsc_class: a @JSCClass
528 *
529 * Get the class name of @jsc_class
530 *
531 * Returns: (transfer none): the name of @jsc_class
532 */
533const char* jsc_class_get_name(JSCClass* jscClass)
534{
535 g_return_val_if_fail(JSC_IS_CLASS(jscClass), nullptr);
536
537 return jscClass->priv->name.data();
538}
539
540/**
541 * jsc_class_get_parent:
542 * @jsc_class: a @JSCClass
543 *
544 * Get the parent class of @jsc_class
545 *
546 * Returns: (transfer none): the parent class of @jsc_class
547 */
548JSCClass* jsc_class_get_parent(JSCClass* jscClass)
549{
550 g_return_val_if_fail(JSC_IS_CLASS(jscClass), nullptr);
551
552 return jscClass->priv->parentClass;
553}
554
555static GRefPtr<JSCValue> jscClassCreateConstructor(JSCClass* jscClass, const char* name, GCallback callback, gpointer userData, GDestroyNotify destroyNotify, GType returnType, Optional<Vector<GType>>&& parameters)
556{
557 // If the constructor doesn't have arguments, we need to swap the fake instance and user data to ensure
558 // user data is the first parameter and fake instance ignored.
559 GRefPtr<GClosure> closure;
560 if (parameters && parameters->isEmpty() && userData)
561 closure = adoptGRef(g_cclosure_new_swap(callback, userData, reinterpret_cast<GClosureNotify>(reinterpret_cast<GCallback>(destroyNotify))));
562 else
563 closure = adoptGRef(g_cclosure_new(callback, userData, reinterpret_cast<GClosureNotify>(reinterpret_cast<GCallback>(destroyNotify))));
564 JSCClassPrivate* priv = jscClass->priv;
565 JSC::ExecState* exec = toJS(jscContextGetJSContext(priv->context));
566 JSC::VM& vm = exec->vm();
567 JSC::JSLockHolder locker(vm);
568 auto* functionObject = JSC::JSCCallbackFunction::create(vm, exec->lexicalGlobalObject(), String::fromUTF8(name),
569 JSC::JSCCallbackFunction::Type::Constructor, jscClass, WTFMove(closure), returnType, WTFMove(parameters));
570 auto constructor = jscContextGetOrCreateValue(priv->context, toRef(functionObject));
571 GRefPtr<JSCValue> prototype = jscContextGetOrCreateValue(priv->context, toRef(priv->prototype.get()));
572 auto nonEnumerable = static_cast<JSCValuePropertyFlags>(JSC_VALUE_PROPERTY_CONFIGURABLE | JSC_VALUE_PROPERTY_WRITABLE);
573 jsc_value_object_define_property_data(constructor.get(), "prototype", nonEnumerable, prototype.get());
574 jsc_value_object_define_property_data(prototype.get(), "constructor", nonEnumerable, constructor.get());
575 priv->constructors.set(name, functionObject);
576 return constructor;
577}
578
579/**
580 * jsc_class_add_constructor: (skip)
581 * @jsc_class: a #JSCClass
582 * @name: (nullable): the constructor name or %NULL
583 * @callback: (scope async): a #GCallback to be called to create an instance of @jsc_class
584 * @user_data: (closure): user data to pass to @callback
585 * @destroy_notify: (nullable): destroy notifier for @user_data
586 * @return_type: the #GType of the constructor return value
587 * @n_params: the number of parameter types to follow or 0 if constructor doesn't receive parameters.
588 * @...: a list of #GType<!-- -->s, one for each parameter.
589 *
590 * Add a constructor to @jsc_class. If @name is %NULL, the class name will be used. When <function>new</function>
591 * is used with the constructor or jsc_value_constructor_call() is called, @callback is invoked receiving the
592 * parameters and @user_data as the last parameter. When the constructor object is cleared in the #JSCClass context,
593 * @destroy_notify is called with @user_data as parameter.
594 *
595 * This function creates the constructor, which needs to be added to an object as a property to be able to use it. Use
596 * jsc_context_set_value() to make the constructor available in the global object.
597 *
598 * Note that the value returned by @callback is adopted by @jsc_class, and the #GDestroyNotify passed to
599 * jsc_context_register_class() is responsible for disposing of it.
600 *
601 * Returns: (transfer full): a #JSCValue representing the class constructor.
602 */
603JSCValue* jsc_class_add_constructor(JSCClass* jscClass, const char* name, GCallback callback, gpointer userData, GDestroyNotify destroyNotify, GType returnType, unsigned paramCount, ...)
604{
605 g_return_val_if_fail(JSC_IS_CLASS(jscClass), nullptr);
606 g_return_val_if_fail(callback, nullptr);
607
608 JSCClassPrivate* priv = jscClass->priv;
609 g_return_val_if_fail(priv->context, nullptr);
610
611 if (!name)
612 name = priv->name.data();
613
614 va_list args;
615 va_start(args, paramCount);
616 Vector<GType> parameters;
617 if (paramCount) {
618 parameters.reserveInitialCapacity(paramCount);
619 for (unsigned i = 0; i < paramCount; ++i)
620 parameters.uncheckedAppend(va_arg(args, GType));
621 }
622 va_end(args);
623
624 return jscClassCreateConstructor(jscClass, name ? name : priv->name.data(), callback, userData, destroyNotify, returnType, WTFMove(parameters)).leakRef();
625
626}
627
628/**
629 * jsc_class_add_constructorv: (rename-to jsc_class_add_constructor)
630 * @jsc_class: a #JSCClass
631 * @name: (nullable): the constructor name or %NULL
632 * @callback: (scope async): a #GCallback to be called to create an instance of @jsc_class
633 * @user_data: (closure): user data to pass to @callback
634 * @destroy_notify: (nullable): destroy notifier for @user_data
635 * @return_type: the #GType of the constructor return value
636 * @n_parameters: the number of parameters
637 * @parameter_types: (nullable) (array length=n_parameters) (element-type GType): a list of #GType<!-- -->s, one for each parameter, or %NULL
638 *
639 * Add a constructor to @jsc_class. If @name is %NULL, the class name will be used. When <function>new</function>
640 * is used with the constructor or jsc_value_constructor_call() is called, @callback is invoked receiving the
641 * parameters and @user_data as the last parameter. When the constructor object is cleared in the #JSCClass context,
642 * @destroy_notify is called with @user_data as parameter.
643 *
644 * This function creates the constructor, which needs to be added to an object as a property to be able to use it. Use
645 * jsc_context_set_value() to make the constructor available in the global object.
646 *
647 * Note that the value returned by @callback is adopted by @jsc_class, and the #GDestroyNotify passed to
648 * jsc_context_register_class() is responsible for disposing of it.
649 *
650 * Returns: (transfer full): a #JSCValue representing the class constructor.
651 */
652JSCValue* jsc_class_add_constructorv(JSCClass* jscClass, const char* name, GCallback callback, gpointer userData, GDestroyNotify destroyNotify, GType returnType, unsigned parametersCount, GType* parameterTypes)
653{
654 g_return_val_if_fail(JSC_IS_CLASS(jscClass), nullptr);
655 g_return_val_if_fail(callback, nullptr);
656 g_return_val_if_fail(!parametersCount || parameterTypes, nullptr);
657
658 JSCClassPrivate* priv = jscClass->priv;
659 g_return_val_if_fail(priv->context, nullptr);
660
661 if (!name)
662 name = priv->name.data();
663
664 Vector<GType> parameters;
665 if (parametersCount) {
666 parameters.reserveInitialCapacity(parametersCount);
667 for (unsigned i = 0; i < parametersCount; ++i)
668 parameters.uncheckedAppend(parameterTypes[i]);
669 }
670
671 return jscClassCreateConstructor(jscClass, name ? name : priv->name.data(), callback, userData, destroyNotify, returnType, WTFMove(parameters)).leakRef();
672}
673
674/**
675 * jsc_class_add_constructor_variadic:
676 * @jsc_class: a #JSCClass
677 * @name: (nullable): the constructor name or %NULL
678 * @callback: (scope async): a #GCallback to be called to create an instance of @jsc_class
679 * @user_data: (closure): user data to pass to @callback
680 * @destroy_notify: (nullable): destroy notifier for @user_data
681 * @return_type: the #GType of the constructor return value
682 *
683 * Add a constructor to @jsc_class. If @name is %NULL, the class name will be used. When <function>new</function>
684 * is used with the constructor or jsc_value_constructor_call() is called, @callback is invoked receiving
685 * a #GPtrArray of #JSCValue<!-- -->s as arguments and @user_data as the last parameter. When the constructor object
686 * is cleared in the #JSCClass context, @destroy_notify is called with @user_data as parameter.
687 *
688 * This function creates the constructor, which needs to be added to an object as a property to be able to use it. Use
689 * jsc_context_set_value() to make the constructor available in the global object.
690 *
691 * Note that the value returned by @callback is adopted by @jsc_class, and the #GDestroyNotify passed to
692 * jsc_context_register_class() is responsible for disposing of it.
693 *
694 * Returns: (transfer full): a #JSCValue representing the class constructor.
695 */
696JSCValue* jsc_class_add_constructor_variadic(JSCClass* jscClass, const char* name, GCallback callback, gpointer userData, GDestroyNotify destroyNotify, GType returnType)
697{
698 g_return_val_if_fail(JSC_IS_CLASS(jscClass), nullptr);
699 g_return_val_if_fail(callback, nullptr);
700
701 JSCClassPrivate* priv = jscClass->priv;
702 g_return_val_if_fail(jscClass->priv->context, nullptr);
703
704 if (!name)
705 name = priv->name.data();
706
707 return jscClassCreateConstructor(jscClass, name ? name : priv->name.data(), callback, userData, destroyNotify, returnType, WTF::nullopt).leakRef();
708}
709
710static void jscClassAddMethod(JSCClass* jscClass, const char* name, GCallback callback, gpointer userData, GDestroyNotify destroyNotify, GType returnType, Optional<Vector<GType>>&& parameters)
711{
712 JSCClassPrivate* priv = jscClass->priv;
713 GRefPtr<GClosure> closure = adoptGRef(g_cclosure_new(callback, userData, reinterpret_cast<GClosureNotify>(reinterpret_cast<GCallback>(destroyNotify))));
714 JSC::ExecState* exec = toJS(jscContextGetJSContext(priv->context));
715 JSC::VM& vm = exec->vm();
716 JSC::JSLockHolder locker(vm);
717 auto* functionObject = toRef(JSC::JSCCallbackFunction::create(vm, exec->lexicalGlobalObject(), String::fromUTF8(name),
718 JSC::JSCCallbackFunction::Type::Method, jscClass, WTFMove(closure), returnType, WTFMove(parameters)));
719 auto method = jscContextGetOrCreateValue(priv->context, functionObject);
720 GRefPtr<JSCValue> prototype = jscContextGetOrCreateValue(priv->context, toRef(priv->prototype.get()));
721 auto nonEnumerable = static_cast<JSCValuePropertyFlags>(JSC_VALUE_PROPERTY_CONFIGURABLE | JSC_VALUE_PROPERTY_WRITABLE);
722 jsc_value_object_define_property_data(prototype.get(), name, nonEnumerable, method.get());
723}
724
725/**
726 * jsc_class_add_method: (skip)
727 * @jsc_class: a #JSCClass
728 * @name: the method name
729 * @callback: (scope async): a #GCallback to be called to invoke method @name of @jsc_class
730 * @user_data: (closure): user data to pass to @callback
731 * @destroy_notify: (nullable): destroy notifier for @user_data
732 * @return_type: the #GType of the method return value, or %G_TYPE_NONE if the method is void.
733 * @n_params: the number of parameter types to follow or 0 if the method doesn't receive parameters.
734 * @...: a list of #GType<!-- -->s, one for each parameter.
735 *
736 * Add method with @name to @jsc_class. When the method is called by JavaScript or jsc_value_object_invoke_method(),
737 * @callback is called receiving the class instance as first parameter, followed by the method parameters and then
738 * @user_data as last parameter. When the method is cleared in the #JSCClass context, @destroy_notify is called with
739 * @user_data as parameter.
740 *
741 * Note that the value returned by @callback must be transfer full. In case of non-refcounted boxed types, you should use
742 * %G_TYPE_POINTER instead of the actual boxed #GType to ensure that the instance owned by #JSCClass is used.
743 * If you really want to return a new copy of the boxed type, use #JSC_TYPE_VALUE and return a #JSCValue created
744 * with jsc_value_new_object() that receives the copy as the instance parameter.
745 */
746void jsc_class_add_method(JSCClass* jscClass, const char* name, GCallback callback, gpointer userData, GDestroyNotify destroyNotify, GType returnType, unsigned paramCount, ...)
747{
748 g_return_if_fail(JSC_IS_CLASS(jscClass));
749 g_return_if_fail(name);
750 g_return_if_fail(callback);
751 g_return_if_fail(jscClass->priv->context);
752
753 va_list args;
754 va_start(args, paramCount);
755 Vector<GType> parameters;
756 if (paramCount) {
757 parameters.reserveInitialCapacity(paramCount);
758 for (unsigned i = 0; i < paramCount; ++i)
759 parameters.uncheckedAppend(va_arg(args, GType));
760 }
761 va_end(args);
762
763 jscClassAddMethod(jscClass, name, callback, userData, destroyNotify, returnType, WTFMove(parameters));
764}
765
766/**
767 * jsc_class_add_methodv: (rename-to jsc_class_add_method)
768 * @jsc_class: a #JSCClass
769 * @name: the method name
770 * @callback: (scope async): a #GCallback to be called to invoke method @name of @jsc_class
771 * @user_data: (closure): user data to pass to @callback
772 * @destroy_notify: (nullable): destroy notifier for @user_data
773 * @return_type: the #GType of the method return value, or %G_TYPE_NONE if the method is void.
774 * @n_parameters: the number of parameter types to follow or 0 if the method doesn't receive parameters.
775 * @parameter_types: (nullable) (array length=n_parameters) (element-type GType): a list of #GType<!-- -->s, one for each parameter, or %NULL
776 *
777 * Add method with @name to @jsc_class. When the method is called by JavaScript or jsc_value_object_invoke_method(),
778 * @callback is called receiving the class instance as first parameter, followed by the method parameters and then
779 * @user_data as last parameter. When the method is cleared in the #JSCClass context, @destroy_notify is called with
780 * @user_data as parameter.
781 *
782 * Note that the value returned by @callback must be transfer full. In case of non-refcounted boxed types, you should use
783 * %G_TYPE_POINTER instead of the actual boxed #GType to ensure that the instance owned by #JSCClass is used.
784 * If you really want to return a new copy of the boxed type, use #JSC_TYPE_VALUE and return a #JSCValue created
785 * with jsc_value_new_object() that receives the copy as the instance parameter.
786 */
787void jsc_class_add_methodv(JSCClass* jscClass, const char* name, GCallback callback, gpointer userData, GDestroyNotify destroyNotify, GType returnType, unsigned parametersCount, GType *parameterTypes)
788{
789 g_return_if_fail(JSC_IS_CLASS(jscClass));
790 g_return_if_fail(name);
791 g_return_if_fail(callback);
792 g_return_if_fail(!parametersCount || parameterTypes);
793 g_return_if_fail(jscClass->priv->context);
794
795 Vector<GType> parameters;
796 if (parametersCount) {
797 parameters.reserveInitialCapacity(parametersCount);
798 for (unsigned i = 0; i < parametersCount; ++i)
799 parameters.uncheckedAppend(parameterTypes[i]);
800 }
801
802 jscClassAddMethod(jscClass, name, callback, userData, destroyNotify, returnType, WTFMove(parameters));
803}
804
805/**
806 * jsc_class_add_method_variadic:
807 * @jsc_class: a #JSCClass
808 * @name: the method name
809 * @callback: (scope async): a #GCallback to be called to invoke method @name of @jsc_class
810 * @user_data: (closure): user data to pass to @callback
811 * @destroy_notify: (nullable): destroy notifier for @user_data
812 * @return_type: the #GType of the method return value, or %G_TYPE_NONE if the method is void.
813 *
814 * Add method with @name to @jsc_class. When the method is called by JavaScript or jsc_value_object_invoke_method(),
815 * @callback is called receiving the class instance as first parameter, followed by a #GPtrArray of #JSCValue<!-- -->s
816 * with the method arguments and then @user_data as last parameter. When the method is cleared in the #JSCClass context,
817 * @destroy_notify is called with @user_data as parameter.
818 *
819 * Note that the value returned by @callback must be transfer full. In case of non-refcounted boxed types, you should use
820 * %G_TYPE_POINTER instead of the actual boxed #GType to ensure that the instance owned by #JSCClass is used.
821 * If you really want to return a new copy of the boxed type, use #JSC_TYPE_VALUE and return a #JSCValue created
822 * with jsc_value_new_object() that receives the copy as the instance parameter.
823 */
824void jsc_class_add_method_variadic(JSCClass* jscClass, const char* name, GCallback callback, gpointer userData, GDestroyNotify destroyNotify, GType returnType)
825{
826 g_return_if_fail(JSC_IS_CLASS(jscClass));
827 g_return_if_fail(name);
828 g_return_if_fail(callback);
829 g_return_if_fail(jscClass->priv->context);
830
831 jscClassAddMethod(jscClass, name, callback, userData, destroyNotify, returnType, WTF::nullopt);
832}
833
834/**
835 * jsc_class_add_property:
836 * @jsc_class: a #JSCClass
837 * @name: the property name
838 * @property_type: the #GType of the property value
839 * @getter: (scope async) (nullable): a #GCallback to be called to get the property value
840 * @setter: (scope async) (nullable): a #GCallback to be called to set the property value
841 * @user_data: (closure): user data to pass to @getter and @setter
842 * @destroy_notify: (nullable): destroy notifier for @user_data
843 *
844 * Add a property with @name to @jsc_class. When the property value needs to be getted, @getter is called
845 * receiving the the class instance as first parameter and @user_data as last parameter. When the property
846 * value needs to be set, @setter is called receiving the the class instance as first parameter, followed
847 * by the value to be set and then @user_data as the last parameter. When the property is cleared in the
848 * #JSCClass context, @destroy_notify is called with @user_data as parameter.
849 *
850 * Note that the value returned by @getter must be transfer full. In case of non-refcounted boxed types, you should use
851 * %G_TYPE_POINTER instead of the actual boxed #GType to ensure that the instance owned by #JSCClass is used.
852 * If you really want to return a new copy of the boxed type, use #JSC_TYPE_VALUE and return a #JSCValue created
853 * with jsc_value_new_object() that receives the copy as the instance parameter.
854 */
855void jsc_class_add_property(JSCClass* jscClass, const char* name, GType propertyType, GCallback getter, GCallback setter, gpointer userData, GDestroyNotify destroyNotify)
856{
857 g_return_if_fail(JSC_IS_CLASS(jscClass));
858 g_return_if_fail(name);
859 g_return_if_fail(propertyType != G_TYPE_INVALID && propertyType != G_TYPE_NONE);
860 g_return_if_fail(getter || setter);
861
862 JSCClassPrivate* priv = jscClass->priv;
863 g_return_if_fail(priv->context);
864
865 GRefPtr<JSCValue> prototype = jscContextGetOrCreateValue(priv->context, toRef(priv->prototype.get()));
866 jsc_value_object_define_property_accessor(prototype.get(), name, JSC_VALUE_PROPERTY_CONFIGURABLE, propertyType, getter, setter, userData, destroyNotify);
867}
868