blob: 19e8d0c111d8c9aceb6bfcf5b43e97d096627a2d [file] [log] [blame]
/*
* 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 <wtf/glib/GUniquePtr.h>
#include <wtf/glib/WTFGType.h>
/**
* 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
* #GCallback<!-- -->s 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<JSC::JSObject> 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<JSCException> m_savedException;
};
static bool isWrappedObject(JSC::JSObject* jsObject)
{
if (jsObject->isGlobalObject())
return jsObject->inherits<JSC::JSCallbackObject<JSC::JSAPIWrapperGlobalObject>>();
return jsObject->inherits<JSC::JSCallbackObject<JSC::JSAPIWrapperObject>>();
}
static JSClassRef wrappedObjectClass(JSC::JSObject* jsObject)
{
ASSERT(isWrappedObject(jsObject));
if (jsObject->isGlobalObject())
return JSC::jsCast<JSC::JSCallbackObject<JSC::JSAPIWrapperGlobalObject>*>(jsObject)->classRef();
return JSC::jsCast<JSC::JSCallbackObject<JSC::JSAPIWrapperObject>*>(jsObject)->classRef();
}
static GRefPtr<JSCContext> 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<JSCValue> 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<JSCValue> 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<char*> properties(enumeratePropertiesFunction(jscClass, context.get(), instance));
if (properties) {
unsigned i = 0;
while (const auto* name = properties.get()[i++]) {
JSRetainPtr<JSStringRef> 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<GParamFlags>(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<GParamFlags>(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<GParamFlags>(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<JSCClass> jscClassCreate(JSCContext* context, const char* name, JSCClass* parentClass, JSCClassVTable* vtable, GDestroyNotify destroyFunction)
{
GRefPtr<JSCClass> 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<char> 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<JSCValue> jscClassCreateConstructor(JSCClass* jscClass, const char* name, GCallback callback, gpointer userData, GDestroyNotify destroyNotify, GType returnType, std::optional<Vector<GType>>&& 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<GClosure> closure;
if (parameters && parameters->isEmpty() && userData)
closure = adoptGRef(g_cclosure_new_swap(callback, userData, reinterpret_cast<GClosureNotify>(reinterpret_cast<GCallback>(destroyNotify))));
else
closure = adoptGRef(g_cclosure_new(callback, userData, reinterpret_cast<GClosureNotify>(reinterpret_cast<GCallback>(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<JSCValue> prototype = jscContextGetOrCreateValue(context.get(), toRef(priv->prototype.get()));
auto nonEnumerable = static_cast<JSCValuePropertyFlags>(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 #GType<!-- -->s, one for each parameter.
*
* Add a constructor to @jsc_class. If @name is %NULL, the class name will be used. When <function>new</function>
* 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<GType> 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 #GType<!-- -->s, one for each parameter, or %NULL
*
* Add a constructor to @jsc_class. If @name is %NULL, the class name will be used. When <function>new</function>
* 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<GType> 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 <function>new</function>
* is used with the constructor or jsc_value_constructor_call() is called, @callback is invoked receiving
* a #GPtrArray of #JSCValue<!-- -->s 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, std::nullopt).leakRef();
}
static void jscClassAddMethod(JSCClass* jscClass, const char* name, GCallback callback, gpointer userData, GDestroyNotify destroyNotify, GType returnType, std::optional<Vector<GType>>&& parameters)
{
JSCClassPrivate* priv = jscClass->priv;
GRefPtr<GClosure> closure = adoptGRef(g_cclosure_new(callback, userData, reinterpret_cast<GClosureNotify>(reinterpret_cast<GCallback>(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<JSCValue> prototype = jscContextGetOrCreateValue(context.get(), toRef(priv->prototype.get()));
auto nonEnumerable = static_cast<JSCValuePropertyFlags>(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 #GType<!-- -->s, 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<GType> 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 #GType<!-- -->s, 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<GType> 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 #JSCValue<!-- -->s
* 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, std::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<JSCValue> prototype = jscContextGetOrCreateValue(context.get(), toRef(priv->prototype.get()));
jscValueAddPropertyAccessor(prototype.get(), name, propertyType, getter, setter, userData, destroyNotify);
}