/* * Copyright (C) 2018 Igalia S.L. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "config.h" #include "JSCClass.h" #include "APICast.h" #include "JSAPIWrapperGlobalObject.h" #include "JSAPIWrapperObject.h" #include "JSCCallbackFunction.h" #include "JSCClassPrivate.h" #include "JSCContextPrivate.h" #include "JSCExceptionPrivate.h" #include "JSCInlines.h" #include "JSCValuePrivate.h" #include "JSCallbackObject.h" #include "JSRetainPtr.h" #include #include /** * SECTION: JSCClass * @short_description: JavaScript custom class * @title: JSCClass * @see_also: JSCContext * * A JSSClass represents a custom JavaScript class registered by the user in a #JSCContext. * It allows to create new JavaScripts objects whose instances are created by the user using * this API. * It's possible to add constructors, properties and methods for a JSSClass by providing * #GCallbacks to implement them. */ enum { PROP_0, PROP_CONTEXT, PROP_NAME, PROP_PARENT }; typedef struct _JSCClassPrivate { JSGlobalContextRef context; CString name; JSClassRef jsClass; JSCClassVTable* vtable; GDestroyNotify destroyFunction; JSCClass* parentClass; JSC::Weak prototype; } JSCClassPrivate; struct _JSCClass { GObject parent; JSCClassPrivate* priv; }; struct _JSCClassClass { GObjectClass parent_class; }; WEBKIT_DEFINE_TYPE(JSCClass, jsc_class, G_TYPE_OBJECT) class VTableExceptionHandler { public: VTableExceptionHandler(JSCContext* context, JSValueRef* exception) : m_context(context) , m_exception(exception) , m_savedException(exception ? jsc_context_get_exception(m_context) : nullptr) { } ~VTableExceptionHandler() { if (!m_exception) return; auto* exception = jsc_context_get_exception(m_context); if (m_savedException.get() == exception) return; *m_exception = jscExceptionGetJSValue(exception); if (m_savedException) jsc_context_throw_exception(m_context, m_savedException.get()); else jsc_context_clear_exception(m_context); } private: JSCContext* m_context { nullptr }; JSValueRef* m_exception { nullptr }; GRefPtr m_savedException; }; static bool isWrappedObject(JSC::JSObject* jsObject) { JSC::JSGlobalObject* globalObject = jsObject->globalObject(); if (jsObject->isGlobalObject()) return jsObject->inherits>(globalObject->vm()); return jsObject->inherits>(globalObject->vm()); } static JSClassRef wrappedObjectClass(JSC::JSObject* jsObject) { ASSERT(isWrappedObject(jsObject)); if (jsObject->isGlobalObject()) return JSC::jsCast*>(jsObject)->classRef(); return JSC::jsCast*>(jsObject)->classRef(); } static GRefPtr jscContextForObject(JSC::JSObject* jsObject) { ASSERT(isWrappedObject(jsObject)); JSC::JSGlobalObject* globalObject = jsObject->globalObject(); if (jsObject->isGlobalObject()) { if (auto* globalScopeExtension = globalObject->globalScopeExtension()) globalObject = JSC::JSScope::objectAtScope(globalScopeExtension)->globalObject(); } return jscContextGetOrCreate(toGlobalRef(globalObject)); } static JSValueRef getProperty(JSContextRef callerContext, JSObjectRef object, JSStringRef propertyName, JSValueRef* exception) { JSC::JSLockHolder locker(toJS(callerContext)); auto* jsObject = toJS(object); if (!isWrappedObject(jsObject)) return nullptr; auto context = jscContextForObject(jsObject); gpointer instance = jscContextWrappedObject(context.get(), object); if (!instance) return nullptr; VTableExceptionHandler exceptionHandler(context.get(), exception); JSClassRef jsClass = wrappedObjectClass(jsObject); for (auto* jscClass = jscContextGetRegisteredClass(context.get(), jsClass); jscClass; jscClass = jscClass->priv->parentClass) { if (!jscClass->priv->vtable) continue; if (auto* getPropertyFunction = jscClass->priv->vtable->get_property) { if (GRefPtr value = adoptGRef(getPropertyFunction(jscClass, context.get(), instance, propertyName->string().utf8().data()))) return jscValueGetJSValue(value.get()); } } return nullptr; } static bool setProperty(JSContextRef callerContext, JSObjectRef object, JSStringRef propertyName, JSValueRef value, JSValueRef* exception) { JSC::JSLockHolder locker(toJS(callerContext)); auto* jsObject = toJS(object); if (!isWrappedObject(jsObject)) return false; auto context = jscContextForObject(jsObject); gpointer instance = jscContextWrappedObject(context.get(), object); if (!instance) return false; VTableExceptionHandler exceptionHandler(context.get(), exception); GRefPtr propertyValue; JSClassRef jsClass = wrappedObjectClass(jsObject); for (auto* jscClass = jscContextGetRegisteredClass(context.get(), jsClass); jscClass; jscClass = jscClass->priv->parentClass) { if (!jscClass->priv->vtable) continue; if (auto* setPropertyFunction = jscClass->priv->vtable->set_property) { if (!propertyValue) propertyValue = jscContextGetOrCreateValue(context.get(), value); if (setPropertyFunction(jscClass, context.get(), instance, propertyName->string().utf8().data(), propertyValue.get())) return true; } } return false; } static bool hasProperty(JSContextRef callerContext, JSObjectRef object, JSStringRef propertyName) { JSC::JSLockHolder locker(toJS(callerContext)); auto* jsObject = toJS(object); if (!isWrappedObject(jsObject)) return false; auto context = jscContextForObject(jsObject); gpointer instance = jscContextWrappedObject(context.get(), object); if (!instance) return false; JSClassRef jsClass = wrappedObjectClass(jsObject); for (auto* jscClass = jscContextGetRegisteredClass(context.get(), jsClass); jscClass; jscClass = jscClass->priv->parentClass) { if (!jscClass->priv->vtable) continue; if (auto* hasPropertyFunction = jscClass->priv->vtable->has_property) { if (hasPropertyFunction(jscClass, context.get(), instance, propertyName->string().utf8().data())) return true; } } return false; } static bool deleteProperty(JSContextRef callerContext, JSObjectRef object, JSStringRef propertyName, JSValueRef* exception) { JSC::JSLockHolder locker(toJS(callerContext)); auto* jsObject = toJS(object); if (!isWrappedObject(jsObject)) return false; auto context = jscContextForObject(jsObject); gpointer instance = jscContextWrappedObject(context.get(), object); if (!instance) return false; VTableExceptionHandler exceptionHandler(context.get(), exception); JSClassRef jsClass = wrappedObjectClass(jsObject); for (auto* jscClass = jscContextGetRegisteredClass(context.get(), jsClass); jscClass; jscClass = jscClass->priv->parentClass) { if (!jscClass->priv->vtable) continue; if (auto* deletePropertyFunction = jscClass->priv->vtable->delete_property) { if (deletePropertyFunction(jscClass, context.get(), instance, propertyName->string().utf8().data())) return true; } } return false; } static void getPropertyNames(JSContextRef callerContext, JSObjectRef object, JSPropertyNameAccumulatorRef propertyNames) { JSC::JSLockHolder locker(toJS(callerContext)); auto* jsObject = toJS(object); if (!isWrappedObject(jsObject)) return; auto context = jscContextForObject(jsObject); gpointer instance = jscContextWrappedObject(context.get(), object); if (!instance) return; JSClassRef jsClass = wrappedObjectClass(jsObject); for (auto* jscClass = jscContextGetRegisteredClass(context.get(), jsClass); jscClass; jscClass = jscClass->priv->parentClass) { if (!jscClass->priv->vtable) continue; if (auto* enumeratePropertiesFunction = jscClass->priv->vtable->enumerate_properties) { GUniquePtr properties(enumeratePropertiesFunction(jscClass, context.get(), instance)); if (properties) { unsigned i = 0; while (const auto* name = properties.get()[i++]) { JSRetainPtr propertyName(Adopt, JSStringCreateWithUTF8CString(name)); JSPropertyNameAccumulatorAddName(propertyNames, propertyName.get()); } } } } } static void jscClassGetProperty(GObject* object, guint propID, GValue* value, GParamSpec* paramSpec) { JSCClass* jscClass = JSC_CLASS(object); switch (propID) { case PROP_NAME: g_value_set_string(value, jscClass->priv->name.data()); break; case PROP_PARENT: g_value_set_object(value, jscClass->priv->parentClass); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propID, paramSpec); } } static void jscClassSetProperty(GObject* object, guint propID, const GValue* value, GParamSpec* paramSpec) { JSCClass* jscClass = JSC_CLASS(object); switch (propID) { case PROP_CONTEXT: jscClass->priv->context = jscContextGetJSContext(JSC_CONTEXT(g_value_get_object(value))); break; case PROP_NAME: jscClass->priv->name = g_value_get_string(value); break; case PROP_PARENT: if (auto* parent = g_value_get_object(value)) jscClass->priv->parentClass = JSC_CLASS(parent); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propID, paramSpec); } } static void jscClassDispose(GObject* object) { JSCClass* jscClass = JSC_CLASS(object); if (jscClass->priv->jsClass) { JSClassRelease(jscClass->priv->jsClass); jscClass->priv->jsClass = nullptr; } G_OBJECT_CLASS(jsc_class_parent_class)->dispose(object); } static void jsc_class_class_init(JSCClassClass* klass) { GObjectClass* objClass = G_OBJECT_CLASS(klass); objClass->dispose = jscClassDispose; objClass->get_property = jscClassGetProperty; objClass->set_property = jscClassSetProperty; /** * JSCClass:context: * * The #JSCContext in which the class was registered. */ g_object_class_install_property(objClass, PROP_CONTEXT, g_param_spec_object( "context", "JSCContext", "JSC Context", JSC_TYPE_CONTEXT, static_cast(WEBKIT_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY))); /** * JSCClass:name: * * The name of the class. */ g_object_class_install_property(objClass, PROP_NAME, g_param_spec_string( "name", "Name", "The class name", nullptr, static_cast(WEBKIT_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY))); /** * JSCClass:parent: * * The parent class or %NULL in case of final classes. */ g_object_class_install_property(objClass, PROP_PARENT, g_param_spec_object( "parent", "Partent", "The parent class", JSC_TYPE_CLASS, static_cast(WEBKIT_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY))); } /** * JSCClassGetPropertyFunction: * @jsc_class: a #JSCClass * @context: a #JSCContext * @instance: the @jsc_class instance * @name: the property name * * The type of get_property in #JSCClassVTable. This is only required when you need to handle * external properties not added to the prototype. * * Returns: (transfer full) (nullable): a #JSCValue or %NULL to forward the request to * the parent class or prototype chain */ /** * JSCClassSetPropertyFunction: * @jsc_class: a #JSCClass * @context: a #JSCContext * @instance: the @jsc_class instance * @name: the property name * @value: the #JSCValue to set * * The type of set_property in #JSCClassVTable. This is only required when you need to handle * external properties not added to the prototype. * * Returns: %TRUE if handled or %FALSE to forward the request to the parent class or prototype chain. */ /** * JSCClassHasPropertyFunction: * @jsc_class: a #JSCClass * @context: a #JSCContext * @instance: the @jsc_class instance * @name: the property name * * The type of has_property in #JSCClassVTable. This is only required when you need to handle * external properties not added to the prototype. * * Returns: %TRUE if @instance has a property with @name or %FALSE to forward the request * to the parent class or prototype chain. */ /** * JSCClassDeletePropertyFunction: * @jsc_class: a #JSCClass * @context: a #JSCContext * @instance: the @jsc_class instance * @name: the property name * * The type of delete_property in #JSCClassVTable. This is only required when you need to handle * external properties not added to the prototype. * * Returns: %TRUE if handled or %FALSE to to forward the request to the parent class or prototype chain. */ /** * JSCClassEnumeratePropertiesFunction: * @jsc_class: a #JSCClass * @context: a #JSCContext * @instance: the @jsc_class instance * * The type of enumerate_properties in #JSCClassVTable. This is only required when you need to handle * external properties not added to the prototype. * * Returns: (array zero-terminated=1) (transfer full) (nullable): a %NULL-terminated array of strings * containing the property names, or %NULL if @instance doesn't have enumerable properties. */ /** * JSCClassVTable: * @get_property: a #JSCClassGetPropertyFunction for getting a property. * @set_property: a #JSCClassSetPropertyFunction for setting a property. * @has_property: a #JSCClassHasPropertyFunction for querying a property. * @delete_property: a #JSCClassDeletePropertyFunction for deleting a property. * @enumerate_properties: a #JSCClassEnumeratePropertiesFunction for enumerating properties. * * Virtual table for a JSCClass. This can be optionally used when registering a #JSCClass in a #JSCContext * to provide a custom implementation for the class. All virtual functions are optional and can be set to * %NULL to fallback to the default implementation. */ GRefPtr jscClassCreate(JSCContext* context, const char* name, JSCClass* parentClass, JSCClassVTable* vtable, GDestroyNotify destroyFunction) { GRefPtr jscClass = adoptGRef(JSC_CLASS(g_object_new(JSC_TYPE_CLASS, "context", context, "name", name, "parent", parentClass, nullptr))); JSCClassPrivate* priv = jscClass->priv; priv->vtable = vtable; priv->destroyFunction = destroyFunction; JSClassDefinition definition = kJSClassDefinitionEmpty; definition.className = priv->name.data(); #define SET_IMPL_IF_NEEDED(definitionFunc, vtableFunc) \ for (auto* klass = jscClass.get(); klass; klass = klass->priv->parentClass) { \ if (klass->priv->vtable && klass->priv->vtable->vtableFunc) { \ definition.definitionFunc = definitionFunc; \ break; \ } \ } SET_IMPL_IF_NEEDED(getProperty, get_property); SET_IMPL_IF_NEEDED(setProperty, set_property); SET_IMPL_IF_NEEDED(hasProperty, has_property); SET_IMPL_IF_NEEDED(deleteProperty, delete_property); SET_IMPL_IF_NEEDED(getPropertyNames, enumerate_properties); #undef SET_IMPL_IF_NEEDED priv->jsClass = JSClassCreate(&definition); GUniquePtr prototypeName(g_strdup_printf("%sPrototype", priv->name.data())); JSClassDefinition prototypeDefinition = kJSClassDefinitionEmpty; prototypeDefinition.className = prototypeName.get(); JSClassRef prototypeClass = JSClassCreate(&prototypeDefinition); priv->prototype = jscContextGetOrCreateJSWrapper(context, prototypeClass); JSClassRelease(prototypeClass); if (priv->parentClass) JSObjectSetPrototype(jscContextGetJSContext(context), toRef(priv->prototype.get()), toRef(priv->parentClass->priv->prototype.get())); return jscClass; } JSClassRef jscClassGetJSClass(JSCClass* jscClass) { return jscClass->priv->jsClass; } JSC::JSObject* jscClassGetOrCreateJSWrapper(JSCClass* jscClass, JSCContext* context, gpointer wrappedObject) { JSCClassPrivate* priv = jscClass->priv; return jscContextGetOrCreateJSWrapper(context, priv->jsClass, toRef(priv->prototype.get()), wrappedObject, priv->destroyFunction); } JSGlobalContextRef jscClassCreateContextWithJSWrapper(JSCClass* jscClass, JSCContext* context, gpointer wrappedObject) { JSCClassPrivate* priv = jscClass->priv; return jscContextCreateContextWithJSWrapper(context, priv->jsClass, toRef(priv->prototype.get()), wrappedObject, priv->destroyFunction); } void jscClassInvalidate(JSCClass* jscClass) { jscClass->priv->context = nullptr; } /** * jsc_class_get_name: * @jsc_class: a @JSCClass * * Get the class name of @jsc_class * * Returns: (transfer none): the name of @jsc_class */ const char* jsc_class_get_name(JSCClass* jscClass) { g_return_val_if_fail(JSC_IS_CLASS(jscClass), nullptr); return jscClass->priv->name.data(); } /** * jsc_class_get_parent: * @jsc_class: a @JSCClass * * Get the parent class of @jsc_class * * Returns: (transfer none): the parent class of @jsc_class */ JSCClass* jsc_class_get_parent(JSCClass* jscClass) { g_return_val_if_fail(JSC_IS_CLASS(jscClass), nullptr); return jscClass->priv->parentClass; } static GRefPtr jscClassCreateConstructor(JSCClass* jscClass, const char* name, GCallback callback, gpointer userData, GDestroyNotify destroyNotify, GType returnType, Optional>&& parameters) { // If the constructor doesn't have arguments, we need to swap the fake instance and user data to ensure // user data is the first parameter and fake instance ignored. GRefPtr closure; if (parameters && parameters->isEmpty() && userData) closure = adoptGRef(g_cclosure_new_swap(callback, userData, reinterpret_cast(reinterpret_cast(destroyNotify)))); else closure = adoptGRef(g_cclosure_new(callback, userData, reinterpret_cast(reinterpret_cast(destroyNotify)))); JSCClassPrivate* priv = jscClass->priv; JSC::JSGlobalObject* globalObject = toJS(priv->context); JSC::VM& vm = globalObject->vm(); JSC::JSLockHolder locker(vm); auto* functionObject = JSC::JSCCallbackFunction::create(vm, globalObject, String::fromUTF8(name), JSC::JSCCallbackFunction::Type::Constructor, jscClass, WTFMove(closure), returnType, WTFMove(parameters)); auto context = jscContextGetOrCreate(priv->context); auto constructor = jscContextGetOrCreateValue(context.get(), toRef(functionObject)); GRefPtr prototype = jscContextGetOrCreateValue(context.get(), toRef(priv->prototype.get())); auto nonEnumerable = static_cast(JSC_VALUE_PROPERTY_CONFIGURABLE | JSC_VALUE_PROPERTY_WRITABLE); jsc_value_object_define_property_data(constructor.get(), "prototype", nonEnumerable, prototype.get()); jsc_value_object_define_property_data(prototype.get(), "constructor", nonEnumerable, constructor.get()); return constructor; } /** * jsc_class_add_constructor: (skip) * @jsc_class: a #JSCClass * @name: (nullable): the constructor name or %NULL * @callback: (scope async): a #GCallback to be called to create an instance of @jsc_class * @user_data: (closure): user data to pass to @callback * @destroy_notify: (nullable): destroy notifier for @user_data * @return_type: the #GType of the constructor return value * @n_params: the number of parameter types to follow or 0 if constructor doesn't receive parameters. * @...: a list of #GTypes, one for each parameter. * * Add a constructor to @jsc_class. If @name is %NULL, the class name will be used. When new * is used with the constructor or jsc_value_constructor_call() is called, @callback is invoked receiving the * parameters and @user_data as the last parameter. When the constructor object is cleared in the #JSCClass context, * @destroy_notify is called with @user_data as parameter. * * This function creates the constructor, which needs to be added to an object as a property to be able to use it. Use * jsc_context_set_value() to make the constructor available in the global object. * * Note that the value returned by @callback is adopted by @jsc_class, and the #GDestroyNotify passed to * jsc_context_register_class() is responsible for disposing of it. * * Returns: (transfer full): a #JSCValue representing the class constructor. */ JSCValue* jsc_class_add_constructor(JSCClass* jscClass, const char* name, GCallback callback, gpointer userData, GDestroyNotify destroyNotify, GType returnType, unsigned paramCount, ...) { g_return_val_if_fail(JSC_IS_CLASS(jscClass), nullptr); g_return_val_if_fail(callback, nullptr); JSCClassPrivate* priv = jscClass->priv; g_return_val_if_fail(priv->context, nullptr); if (!name) name = priv->name.data(); va_list args; va_start(args, paramCount); Vector parameters; if (paramCount) { parameters.reserveInitialCapacity(paramCount); for (unsigned i = 0; i < paramCount; ++i) parameters.uncheckedAppend(va_arg(args, GType)); } va_end(args); return jscClassCreateConstructor(jscClass, name ? name : priv->name.data(), callback, userData, destroyNotify, returnType, WTFMove(parameters)).leakRef(); } /** * jsc_class_add_constructorv: (rename-to jsc_class_add_constructor) * @jsc_class: a #JSCClass * @name: (nullable): the constructor name or %NULL * @callback: (scope async): a #GCallback to be called to create an instance of @jsc_class * @user_data: (closure): user data to pass to @callback * @destroy_notify: (nullable): destroy notifier for @user_data * @return_type: the #GType of the constructor return value * @n_parameters: the number of parameters * @parameter_types: (nullable) (array length=n_parameters) (element-type GType): a list of #GTypes, one for each parameter, or %NULL * * Add a constructor to @jsc_class. If @name is %NULL, the class name will be used. When new * is used with the constructor or jsc_value_constructor_call() is called, @callback is invoked receiving the * parameters and @user_data as the last parameter. When the constructor object is cleared in the #JSCClass context, * @destroy_notify is called with @user_data as parameter. * * This function creates the constructor, which needs to be added to an object as a property to be able to use it. Use * jsc_context_set_value() to make the constructor available in the global object. * * Note that the value returned by @callback is adopted by @jsc_class, and the #GDestroyNotify passed to * jsc_context_register_class() is responsible for disposing of it. * * Returns: (transfer full): a #JSCValue representing the class constructor. */ JSCValue* jsc_class_add_constructorv(JSCClass* jscClass, const char* name, GCallback callback, gpointer userData, GDestroyNotify destroyNotify, GType returnType, unsigned parametersCount, GType* parameterTypes) { g_return_val_if_fail(JSC_IS_CLASS(jscClass), nullptr); g_return_val_if_fail(callback, nullptr); g_return_val_if_fail(!parametersCount || parameterTypes, nullptr); JSCClassPrivate* priv = jscClass->priv; g_return_val_if_fail(priv->context, nullptr); if (!name) name = priv->name.data(); Vector parameters; if (parametersCount) { parameters.reserveInitialCapacity(parametersCount); for (unsigned i = 0; i < parametersCount; ++i) parameters.uncheckedAppend(parameterTypes[i]); } return jscClassCreateConstructor(jscClass, name ? name : priv->name.data(), callback, userData, destroyNotify, returnType, WTFMove(parameters)).leakRef(); } /** * jsc_class_add_constructor_variadic: * @jsc_class: a #JSCClass * @name: (nullable): the constructor name or %NULL * @callback: (scope async): a #GCallback to be called to create an instance of @jsc_class * @user_data: (closure): user data to pass to @callback * @destroy_notify: (nullable): destroy notifier for @user_data * @return_type: the #GType of the constructor return value * * Add a constructor to @jsc_class. If @name is %NULL, the class name will be used. When new * is used with the constructor or jsc_value_constructor_call() is called, @callback is invoked receiving * a #GPtrArray of #JSCValues as arguments and @user_data as the last parameter. When the constructor object * is cleared in the #JSCClass context, @destroy_notify is called with @user_data as parameter. * * This function creates the constructor, which needs to be added to an object as a property to be able to use it. Use * jsc_context_set_value() to make the constructor available in the global object. * * Note that the value returned by @callback is adopted by @jsc_class, and the #GDestroyNotify passed to * jsc_context_register_class() is responsible for disposing of it. * * Returns: (transfer full): a #JSCValue representing the class constructor. */ JSCValue* jsc_class_add_constructor_variadic(JSCClass* jscClass, const char* name, GCallback callback, gpointer userData, GDestroyNotify destroyNotify, GType returnType) { g_return_val_if_fail(JSC_IS_CLASS(jscClass), nullptr); g_return_val_if_fail(callback, nullptr); JSCClassPrivate* priv = jscClass->priv; g_return_val_if_fail(jscClass->priv->context, nullptr); if (!name) name = priv->name.data(); return jscClassCreateConstructor(jscClass, name ? name : priv->name.data(), callback, userData, destroyNotify, returnType, WTF::nullopt).leakRef(); } static void jscClassAddMethod(JSCClass* jscClass, const char* name, GCallback callback, gpointer userData, GDestroyNotify destroyNotify, GType returnType, Optional>&& parameters) { JSCClassPrivate* priv = jscClass->priv; GRefPtr closure = adoptGRef(g_cclosure_new(callback, userData, reinterpret_cast(reinterpret_cast(destroyNotify)))); JSC::JSGlobalObject* globalObject = toJS(priv->context); JSC::VM& vm = globalObject->vm(); JSC::JSLockHolder locker(vm); auto* functionObject = toRef(JSC::JSCCallbackFunction::create(vm, globalObject, String::fromUTF8(name), JSC::JSCCallbackFunction::Type::Method, jscClass, WTFMove(closure), returnType, WTFMove(parameters))); auto context = jscContextGetOrCreate(priv->context); auto method = jscContextGetOrCreateValue(context.get(), functionObject); GRefPtr prototype = jscContextGetOrCreateValue(context.get(), toRef(priv->prototype.get())); auto nonEnumerable = static_cast(JSC_VALUE_PROPERTY_CONFIGURABLE | JSC_VALUE_PROPERTY_WRITABLE); jsc_value_object_define_property_data(prototype.get(), name, nonEnumerable, method.get()); } /** * jsc_class_add_method: (skip) * @jsc_class: a #JSCClass * @name: the method name * @callback: (scope async): a #GCallback to be called to invoke method @name of @jsc_class * @user_data: (closure): user data to pass to @callback * @destroy_notify: (nullable): destroy notifier for @user_data * @return_type: the #GType of the method return value, or %G_TYPE_NONE if the method is void. * @n_params: the number of parameter types to follow or 0 if the method doesn't receive parameters. * @...: a list of #GTypes, one for each parameter. * * Add method with @name to @jsc_class. When the method is called by JavaScript or jsc_value_object_invoke_method(), * @callback is called receiving the class instance as first parameter, followed by the method parameters and then * @user_data as last parameter. When the method is cleared in the #JSCClass context, @destroy_notify is called with * @user_data as parameter. * * Note that the value returned by @callback must be transfer full. In case of non-refcounted boxed types, you should use * %G_TYPE_POINTER instead of the actual boxed #GType to ensure that the instance owned by #JSCClass is used. * If you really want to return a new copy of the boxed type, use #JSC_TYPE_VALUE and return a #JSCValue created * with jsc_value_new_object() that receives the copy as the instance parameter. */ void jsc_class_add_method(JSCClass* jscClass, const char* name, GCallback callback, gpointer userData, GDestroyNotify destroyNotify, GType returnType, unsigned paramCount, ...) { g_return_if_fail(JSC_IS_CLASS(jscClass)); g_return_if_fail(name); g_return_if_fail(callback); g_return_if_fail(jscClass->priv->context); va_list args; va_start(args, paramCount); Vector parameters; if (paramCount) { parameters.reserveInitialCapacity(paramCount); for (unsigned i = 0; i < paramCount; ++i) parameters.uncheckedAppend(va_arg(args, GType)); } va_end(args); jscClassAddMethod(jscClass, name, callback, userData, destroyNotify, returnType, WTFMove(parameters)); } /** * jsc_class_add_methodv: (rename-to jsc_class_add_method) * @jsc_class: a #JSCClass * @name: the method name * @callback: (scope async): a #GCallback to be called to invoke method @name of @jsc_class * @user_data: (closure): user data to pass to @callback * @destroy_notify: (nullable): destroy notifier for @user_data * @return_type: the #GType of the method return value, or %G_TYPE_NONE if the method is void. * @n_parameters: the number of parameter types to follow or 0 if the method doesn't receive parameters. * @parameter_types: (nullable) (array length=n_parameters) (element-type GType): a list of #GTypes, one for each parameter, or %NULL * * Add method with @name to @jsc_class. When the method is called by JavaScript or jsc_value_object_invoke_method(), * @callback is called receiving the class instance as first parameter, followed by the method parameters and then * @user_data as last parameter. When the method is cleared in the #JSCClass context, @destroy_notify is called with * @user_data as parameter. * * Note that the value returned by @callback must be transfer full. In case of non-refcounted boxed types, you should use * %G_TYPE_POINTER instead of the actual boxed #GType to ensure that the instance owned by #JSCClass is used. * If you really want to return a new copy of the boxed type, use #JSC_TYPE_VALUE and return a #JSCValue created * with jsc_value_new_object() that receives the copy as the instance parameter. */ void jsc_class_add_methodv(JSCClass* jscClass, const char* name, GCallback callback, gpointer userData, GDestroyNotify destroyNotify, GType returnType, unsigned parametersCount, GType *parameterTypes) { g_return_if_fail(JSC_IS_CLASS(jscClass)); g_return_if_fail(name); g_return_if_fail(callback); g_return_if_fail(!parametersCount || parameterTypes); g_return_if_fail(jscClass->priv->context); Vector parameters; if (parametersCount) { parameters.reserveInitialCapacity(parametersCount); for (unsigned i = 0; i < parametersCount; ++i) parameters.uncheckedAppend(parameterTypes[i]); } jscClassAddMethod(jscClass, name, callback, userData, destroyNotify, returnType, WTFMove(parameters)); } /** * jsc_class_add_method_variadic: * @jsc_class: a #JSCClass * @name: the method name * @callback: (scope async): a #GCallback to be called to invoke method @name of @jsc_class * @user_data: (closure): user data to pass to @callback * @destroy_notify: (nullable): destroy notifier for @user_data * @return_type: the #GType of the method return value, or %G_TYPE_NONE if the method is void. * * Add method with @name to @jsc_class. When the method is called by JavaScript or jsc_value_object_invoke_method(), * @callback is called receiving the class instance as first parameter, followed by a #GPtrArray of #JSCValues * with the method arguments and then @user_data as last parameter. When the method is cleared in the #JSCClass context, * @destroy_notify is called with @user_data as parameter. * * Note that the value returned by @callback must be transfer full. In case of non-refcounted boxed types, you should use * %G_TYPE_POINTER instead of the actual boxed #GType to ensure that the instance owned by #JSCClass is used. * If you really want to return a new copy of the boxed type, use #JSC_TYPE_VALUE and return a #JSCValue created * with jsc_value_new_object() that receives the copy as the instance parameter. */ void jsc_class_add_method_variadic(JSCClass* jscClass, const char* name, GCallback callback, gpointer userData, GDestroyNotify destroyNotify, GType returnType) { g_return_if_fail(JSC_IS_CLASS(jscClass)); g_return_if_fail(name); g_return_if_fail(callback); g_return_if_fail(jscClass->priv->context); jscClassAddMethod(jscClass, name, callback, userData, destroyNotify, returnType, WTF::nullopt); } /** * jsc_class_add_property: * @jsc_class: a #JSCClass * @name: the property name * @property_type: the #GType of the property value * @getter: (scope async) (nullable): a #GCallback to be called to get the property value * @setter: (scope async) (nullable): a #GCallback to be called to set the property value * @user_data: (closure): user data to pass to @getter and @setter * @destroy_notify: (nullable): destroy notifier for @user_data * * Add a property with @name to @jsc_class. When the property value needs to be getted, @getter is called * receiving the the class instance as first parameter and @user_data as last parameter. When the property * value needs to be set, @setter is called receiving the the class instance as first parameter, followed * by the value to be set and then @user_data as the last parameter. When the property is cleared in the * #JSCClass context, @destroy_notify is called with @user_data as parameter. * * Note that the value returned by @getter must be transfer full. In case of non-refcounted boxed types, you should use * %G_TYPE_POINTER instead of the actual boxed #GType to ensure that the instance owned by #JSCClass is used. * If you really want to return a new copy of the boxed type, use #JSC_TYPE_VALUE and return a #JSCValue created * with jsc_value_new_object() that receives the copy as the instance parameter. */ void jsc_class_add_property(JSCClass* jscClass, const char* name, GType propertyType, GCallback getter, GCallback setter, gpointer userData, GDestroyNotify destroyNotify) { g_return_if_fail(JSC_IS_CLASS(jscClass)); g_return_if_fail(name); g_return_if_fail(propertyType != G_TYPE_INVALID && propertyType != G_TYPE_NONE); g_return_if_fail(getter || setter); JSCClassPrivate* priv = jscClass->priv; g_return_if_fail(priv->context); auto context = jscContextGetOrCreate(priv->context); GRefPtr prototype = jscContextGetOrCreateValue(context.get(), toRef(priv->prototype.get())); jsc_value_object_define_property_accessor(prototype.get(), name, JSC_VALUE_PROPERTY_CONFIGURABLE, propertyType, getter, setter, userData, destroyNotify); }