#include "config.h"
#include "JSCClass.h"
#include "APICast.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>
enum {
PROP_0,
PROP_CONTEXT,
PROP_NAME,
PROP_PARENT
};
typedef struct _JSCClassPrivate {
JSCContext* context;
CString name;
JSClassRef jsClass;
JSCClassVTable* vtable;
GDestroyNotify destroyFunction;
JSCClass* parentClass;
JSC::Weak<JSC::JSObject> prototype;
HashMap<CString, JSC::Weak<JSC::JSObject>> constructors;
} 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 JSValueRef getProperty(JSContextRef callerContext, JSObjectRef object, JSStringRef propertyName, JSValueRef* exception)
{
JSC::JSLockHolder locker(toJS(callerContext));
auto* jsObject = toJS(object);
auto context = jscContextGetOrCreate(toGlobalRef(jsObject->globalObject()->globalExec()));
auto* jsContext = jscContextGetJSContext(context.get());
if (!jsObject->inherits<JSC::JSCallbackObject<JSC::JSAPIWrapperObject>>(toJS(jsContext)->vm()))
return nullptr;
gpointer instance = jscContextWrappedObject(context.get(), object);
if (!instance)
return nullptr;
VTableExceptionHandler exceptionHandler(context.get(), exception);
JSClassRef jsClass = JSC::jsCast<JSC::JSCallbackObject<JSC::JSAPIWrapperObject>*>(jsObject)->classRef();
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);
auto context = jscContextGetOrCreate(toGlobalRef(jsObject->globalObject()->globalExec()));
auto* jsContext = jscContextGetJSContext(context.get());
if (!jsObject->inherits<JSC::JSCallbackObject<JSC::JSAPIWrapperObject>>(toJS(jsContext)->vm()))
return false;
gpointer instance = jscContextWrappedObject(context.get(), object);
if (!instance)
return false;
VTableExceptionHandler exceptionHandler(context.get(), exception);
GRefPtr<JSCValue> propertyValue;
JSClassRef jsClass = JSC::jsCast<JSC::JSCallbackObject<JSC::JSAPIWrapperObject>*>(jsObject)->classRef();
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);
auto context = jscContextGetOrCreate(toGlobalRef(jsObject->globalObject()->globalExec()));
auto* jsContext = jscContextGetJSContext(context.get());
if (!jsObject->inherits<JSC::JSCallbackObject<JSC::JSAPIWrapperObject>>(toJS(jsContext)->vm()))
return false;
gpointer instance = jscContextWrappedObject(context.get(), object);
if (!instance)
return false;
JSClassRef jsClass = JSC::jsCast<JSC::JSCallbackObject<JSC::JSAPIWrapperObject>*>(jsObject)->classRef();
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);
auto context = jscContextGetOrCreate(toGlobalRef(jsObject->globalObject()->globalExec()));
auto* jsContext = jscContextGetJSContext(context.get());
if (!jsObject->inherits<JSC::JSCallbackObject<JSC::JSAPIWrapperObject>>(toJS(jsContext)->vm()))
return false;
gpointer instance = jscContextWrappedObject(context.get(), object);
if (!instance)
return false;
VTableExceptionHandler exceptionHandler(context.get(), exception);
JSClassRef jsClass = JSC::jsCast<JSC::JSCallbackObject<JSC::JSAPIWrapperObject>*>(jsObject)->classRef();
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);
auto context = jscContextGetOrCreate(toGlobalRef(jsObject->globalObject()->globalExec()));
auto* jsContext = jscContextGetJSContext(context.get());
if (!jsObject->inherits<JSC::JSCallbackObject<JSC::JSAPIWrapperObject>>(toJS(jsContext)->vm()))
return;
gpointer instance = jscContextWrappedObject(context.get(), object);
if (!instance)
return;
JSClassRef jsClass = JSC::jsCast<JSC::JSCallbackObject<JSC::JSAPIWrapperObject>*>(jsObject)->classRef();
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_CONTEXT:
g_value_set_object(value, jscClass->priv->context);
break;
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 = 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;
g_object_class_install_property(objClass,
PROP_CONTEXT,
g_param_spec_object(
"context",
"JSCContext",
"JSC Context",
JSC_TYPE_CONTEXT,
static_cast<GParamFlags>(WEBKIT_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)));
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)));
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)));
}
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(priv->context, prototypeClass);
JSClassRelease(prototypeClass);
if (priv->parentClass)
JSObjectSetPrototype(jscContextGetJSContext(priv->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, gpointer wrappedObject)
{
JSCClassPrivate* priv = jscClass->priv;
return jscContextGetOrCreateJSWrapper(priv->context, priv->jsClass, toRef(priv->prototype.get()), wrappedObject, priv->destroyFunction);
}
void jscClassInvalidate(JSCClass* jscClass)
{
jscClass->priv->context = nullptr;
}
const char* jsc_class_get_name(JSCClass* jscClass)
{
g_return_val_if_fail(JSC_IS_CLASS(jscClass), nullptr);
return jscClass->priv->name.data();
}
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)
{
JSCClassPrivate* priv = jscClass->priv;
GRefPtr<GClosure> closure = adoptGRef(g_cclosure_new(callback, userData, reinterpret_cast<GClosureNotify>(reinterpret_cast<GCallback>(destroyNotify))));
JSC::ExecState* exec = toJS(jscContextGetJSContext(priv->context));
JSC::VM& vm = exec->vm();
JSC::JSLockHolder locker(vm);
auto* functionObject = JSC::JSCCallbackFunction::create(vm, exec->lexicalGlobalObject(), String::fromUTF8(name),
JSC::JSCCallbackFunction::Type::Constructor, jscClass, WTFMove(closure), returnType, WTFMove(parameters));
auto constructor = jscContextGetOrCreateValue(priv->context, toRef(functionObject));
GRefPtr<JSCValue> prototype = jscContextGetOrCreateValue(priv->context, 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());
priv->constructors.set(name, functionObject);
return 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();
}
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();
}
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::ExecState* exec = toJS(jscContextGetJSContext(priv->context));
JSC::VM& vm = exec->vm();
JSC::JSLockHolder locker(vm);
auto* functionObject = toRef(JSC::JSCCallbackFunction::create(vm, exec->lexicalGlobalObject(), String::fromUTF8(name),
JSC::JSCCallbackFunction::Type::Method, jscClass, WTFMove(closure), returnType, WTFMove(parameters)));
auto method = jscContextGetOrCreateValue(priv->context, functionObject);
GRefPtr<JSCValue> prototype = jscContextGetOrCreateValue(priv->context, 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());
}
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));
}
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));
}
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);
}
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);
GRefPtr<JSCValue> prototype = jscContextGetOrCreateValue(priv->context, toRef(priv->prototype.get()));
jsc_value_object_define_property_accessor(prototype.get(), name, JSC_VALUE_PROPERTY_CONFIGURABLE, propertyType, getter, setter, userData, destroyNotify);
}