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