Skip to content

Commit a66da84

Browse files
committed
[GLIB] Make it possible to handle JSCClass external properties not added to the prototype
https://bugs.webkit.org/show_bug.cgi?id=184687 Reviewed by Michael Catanzaro. Source/JavaScriptCore: Add JSCClassVTable that can be optionally passed to jsc_context_register_class() to provide implmentations for JSClassDefinition. This is required to implement dynamic properties that can't be added with jsc_class_add_property() for example to implement something like imports object in seed/gjs. * API/glib/JSCClass.cpp: (VTableExceptionHandler::VTableExceptionHandler): Helper class to handle the exceptions in vtable functions that can throw exceptions. (VTableExceptionHandler::~VTableExceptionHandler): (getProperty): Iterate the class chain to call get_property function. (setProperty): Iterate the class chain to call set_property function. (hasProperty): Iterate the class chain to call has_property function. (deleteProperty): Iterate the class chain to call delete_property function. (getPropertyNames): Iterate the class chain to call enumerate_properties function. (jsc_class_class_init): Remove constructed implementation, since we need to initialize the JSClassDefinition in jscClassCreate now. (jscClassCreate): Receive an optional JSCClassVTable that is used to initialize the JSClassDefinition. * API/glib/JSCClass.h: * API/glib/JSCClassPrivate.h: * API/glib/JSCContext.cpp: (jscContextGetRegisteredClass): Helper to get the JSCClass for a given JSClassRef. (jsc_context_register_class): Add JSCClassVTable parameter. * API/glib/JSCContext.h: * API/glib/JSCContextPrivate.h: * API/glib/JSCWrapperMap.cpp: (JSC::WrapperMap::registeredClass const): Get the JSCClass for a given JSClassRef. * API/glib/JSCWrapperMap.h: * API/glib/docs/jsc-glib-4.0-sections.txt: Add new symbols. Tools: Add test cases for the new API. * TestWebKitAPI/Tests/JavaScriptCore/glib/TestJSC.cpp: (fooCreate): (fooFree): (fooGetProperty): (fooSetProperty): (testJSCPromises): (testJSCGarbageCollector): (testsJSCVirtualMachine): * TestWebKitAPI/Tests/WebKitGLib/WebProcessTest.cpp: (windowObjectClearedCallback): Canonical link: https://commits.webkit.org/200239@main git-svn-id: https://svn.webkit.org/repository/webkit/trunk@230753 268f45cc-cd09-0410-ab3c-d52691b4dbfc
1 parent 55d5831 commit a66da84

13 files changed

Lines changed: 817 additions & 45 deletions

File tree

Source/JavaScriptCore/API/glib/JSCClass.cpp

Lines changed: 299 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,16 @@
2020
#include "config.h"
2121
#include "JSCClass.h"
2222

23+
#include "APICast.h"
24+
#include "JSAPIWrapperObject.h"
2325
#include "JSCCallbackFunction.h"
2426
#include "JSCClassPrivate.h"
2527
#include "JSCContextPrivate.h"
28+
#include "JSCExceptionPrivate.h"
2629
#include "JSCInlines.h"
2730
#include "JSCValuePrivate.h"
31+
#include "JSCallbackObject.h"
32+
#include "JSRetainPtr.h"
2833
#include <wtf/glib/GUniquePtr.h>
2934
#include <wtf/glib/WTFGType.h>
3035

@@ -53,6 +58,7 @@ typedef struct _JSCClassPrivate {
5358
JSCContext* context;
5459
CString name;
5560
JSClassRef jsClass;
61+
JSCClassVTable* vtable;
5662
GDestroyNotify destroyFunction;
5763
JSCClass* parentClass;
5864
JSC::Weak<JSC::JSObject> prototype;
@@ -71,6 +77,182 @@ struct _JSCClassClass {
7177

7278
WEBKIT_DEFINE_TYPE(JSCClass, jsc_class, G_TYPE_OBJECT)
7379

80+
class VTableExceptionHandler {
81+
public:
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+
105+
private:
106+
JSCContext* m_context { nullptr };
107+
JSValueRef* m_exception { nullptr };
108+
GRefPtr<JSCException> m_savedException;
109+
};
110+
111+
static JSValueRef getProperty(JSContextRef callerContext, JSObjectRef object, JSStringRef propertyName, JSValueRef* exception)
112+
{
113+
JSC::JSLockHolder locker(toJS(callerContext));
114+
auto* jsObject = toJS(object);
115+
auto context = jscContextGetOrCreate(toGlobalRef(jsObject->globalObject()->globalExec()));
116+
auto* jsContext = jscContextGetJSContext(context.get());
117+
if (!jsObject->inherits<JSC::JSCallbackObject<JSC::JSAPIWrapperObject>>(toJS(jsContext)->vm()))
118+
return nullptr;
119+
120+
gpointer instance = jscContextWrappedObject(context.get(), object);
121+
if (!instance)
122+
return nullptr;
123+
124+
VTableExceptionHandler exceptionHandler(context.get(), exception);
125+
126+
JSClassRef jsClass = JSC::jsCast<JSC::JSCallbackObject<JSC::JSAPIWrapperObject>*>(jsObject)->classRef();
127+
for (auto* jscClass = jscContextGetRegisteredClass(context.get(), jsClass); jscClass; jscClass = jscClass->priv->parentClass) {
128+
if (!jscClass->priv->vtable)
129+
continue;
130+
131+
if (auto* getPropertyFunction = jscClass->priv->vtable->get_property) {
132+
if (GRefPtr<JSCValue> value = adoptGRef(getPropertyFunction(jscClass, context.get(), instance, propertyName->string().utf8().data())))
133+
return jscValueGetJSValue(value.get());
134+
}
135+
}
136+
return nullptr;
137+
}
138+
139+
static bool setProperty(JSContextRef callerContext, JSObjectRef object, JSStringRef propertyName, JSValueRef value, JSValueRef* exception)
140+
{
141+
JSC::JSLockHolder locker(toJS(callerContext));
142+
auto* jsObject = toJS(object);
143+
auto context = jscContextGetOrCreate(toGlobalRef(jsObject->globalObject()->globalExec()));
144+
auto* jsContext = jscContextGetJSContext(context.get());
145+
if (!jsObject->inherits<JSC::JSCallbackObject<JSC::JSAPIWrapperObject>>(toJS(jsContext)->vm()))
146+
return false;
147+
148+
gpointer instance = jscContextWrappedObject(context.get(), object);
149+
if (!instance)
150+
return false;
151+
152+
VTableExceptionHandler exceptionHandler(context.get(), exception);
153+
154+
GRefPtr<JSCValue> propertyValue;
155+
JSClassRef jsClass = JSC::jsCast<JSC::JSCallbackObject<JSC::JSAPIWrapperObject>*>(jsObject)->classRef();
156+
for (auto* jscClass = jscContextGetRegisteredClass(context.get(), jsClass); jscClass; jscClass = jscClass->priv->parentClass) {
157+
if (!jscClass->priv->vtable)
158+
continue;
159+
160+
if (auto* setPropertyFunction = jscClass->priv->vtable->set_property) {
161+
if (!propertyValue)
162+
propertyValue = jscContextGetOrCreateValue(context.get(), value);
163+
if (setPropertyFunction(jscClass, context.get(), instance, propertyName->string().utf8().data(), propertyValue.get()))
164+
return true;
165+
}
166+
}
167+
return false;
168+
}
169+
170+
static bool hasProperty(JSContextRef callerContext, JSObjectRef object, JSStringRef propertyName)
171+
{
172+
JSC::JSLockHolder locker(toJS(callerContext));
173+
auto* jsObject = toJS(object);
174+
auto context = jscContextGetOrCreate(toGlobalRef(jsObject->globalObject()->globalExec()));
175+
auto* jsContext = jscContextGetJSContext(context.get());
176+
if (!jsObject->inherits<JSC::JSCallbackObject<JSC::JSAPIWrapperObject>>(toJS(jsContext)->vm()))
177+
return false;
178+
179+
gpointer instance = jscContextWrappedObject(context.get(), object);
180+
if (!instance)
181+
return false;
182+
183+
JSClassRef jsClass = JSC::jsCast<JSC::JSCallbackObject<JSC::JSAPIWrapperObject>*>(jsObject)->classRef();
184+
for (auto* jscClass = jscContextGetRegisteredClass(context.get(), jsClass); jscClass; jscClass = jscClass->priv->parentClass) {
185+
if (!jscClass->priv->vtable)
186+
continue;
187+
188+
if (auto* hasPropertyFunction = jscClass->priv->vtable->has_property) {
189+
if (hasPropertyFunction(jscClass, context.get(), instance, propertyName->string().utf8().data()))
190+
return true;
191+
}
192+
}
193+
194+
return false;
195+
}
196+
197+
static bool deleteProperty(JSContextRef callerContext, JSObjectRef object, JSStringRef propertyName, JSValueRef* exception)
198+
{
199+
JSC::JSLockHolder locker(toJS(callerContext));
200+
auto* jsObject = toJS(object);
201+
auto context = jscContextGetOrCreate(toGlobalRef(jsObject->globalObject()->globalExec()));
202+
auto* jsContext = jscContextGetJSContext(context.get());
203+
if (!jsObject->inherits<JSC::JSCallbackObject<JSC::JSAPIWrapperObject>>(toJS(jsContext)->vm()))
204+
return false;
205+
206+
gpointer instance = jscContextWrappedObject(context.get(), object);
207+
if (!instance)
208+
return false;
209+
210+
VTableExceptionHandler exceptionHandler(context.get(), exception);
211+
212+
JSClassRef jsClass = JSC::jsCast<JSC::JSCallbackObject<JSC::JSAPIWrapperObject>*>(jsObject)->classRef();
213+
for (auto* jscClass = jscContextGetRegisteredClass(context.get(), jsClass); jscClass; jscClass = jscClass->priv->parentClass) {
214+
if (!jscClass->priv->vtable)
215+
continue;
216+
217+
if (auto* deletePropertyFunction = jscClass->priv->vtable->delete_property) {
218+
if (deletePropertyFunction(jscClass, context.get(), instance, propertyName->string().utf8().data()))
219+
return true;
220+
}
221+
}
222+
return false;
223+
}
224+
225+
static void getPropertyNames(JSContextRef callerContext, JSObjectRef object, JSPropertyNameAccumulatorRef propertyNames)
226+
{
227+
JSC::JSLockHolder locker(toJS(callerContext));
228+
auto* jsObject = toJS(object);
229+
auto context = jscContextGetOrCreate(toGlobalRef(jsObject->globalObject()->globalExec()));
230+
auto* jsContext = jscContextGetJSContext(context.get());
231+
if (!jsObject->inherits<JSC::JSCallbackObject<JSC::JSAPIWrapperObject>>(toJS(jsContext)->vm()))
232+
return;
233+
234+
gpointer instance = jscContextWrappedObject(context.get(), object);
235+
if (!instance)
236+
return;
237+
238+
JSClassRef jsClass = JSC::jsCast<JSC::JSCallbackObject<JSC::JSAPIWrapperObject>*>(jsObject)->classRef();
239+
for (auto* jscClass = jscContextGetRegisteredClass(context.get(), jsClass); jscClass; jscClass = jscClass->priv->parentClass) {
240+
if (!jscClass->priv->vtable)
241+
continue;
242+
243+
if (auto* enumeratePropertiesFunction = jscClass->priv->vtable->enumerate_properties) {
244+
GUniquePtr<char*> properties(enumeratePropertiesFunction(jscClass, context.get(), instance));
245+
if (properties) {
246+
unsigned i = 0;
247+
while (const auto* name = properties.get()[i++]) {
248+
JSRetainPtr<JSStringRef> propertyName(Adopt, JSStringCreateWithUTF8CString(name));
249+
JSPropertyNameAccumulatorAddName(propertyNames, propertyName.get());
250+
}
251+
}
252+
}
253+
}
254+
}
255+
74256
static void jscClassGetProperty(GObject* object, guint propID, GValue* value, GParamSpec* paramSpec)
75257
{
76258
JSCClass* jscClass = JSC_CLASS(object);
@@ -121,30 +303,9 @@ static void jscClassDispose(GObject* object)
121303
G_OBJECT_CLASS(jsc_class_parent_class)->dispose(object);
122304
}
123305

124-
static void jscClassConstructed(GObject* object)
125-
{
126-
G_OBJECT_CLASS(jsc_class_parent_class)->constructed(object);
127-
128-
JSCClassPrivate* priv = JSC_CLASS(object)->priv;
129-
JSClassDefinition definition = kJSClassDefinitionEmpty;
130-
definition.className = priv->name.data();
131-
priv->jsClass = JSClassCreate(&definition);
132-
133-
GUniquePtr<char> prototypeName(g_strdup_printf("%sPrototype", priv->name.data()));
134-
JSClassDefinition prototypeDefinition = kJSClassDefinitionEmpty;
135-
prototypeDefinition.className = prototypeName.get();
136-
JSClassRef prototypeClass = JSClassCreate(&prototypeDefinition);
137-
priv->prototype = jscContextGetOrCreateJSWrapper(priv->context, prototypeClass);
138-
JSClassRelease(prototypeClass);
139-
140-
if (priv->parentClass)
141-
JSObjectSetPrototype(jscContextGetJSContext(priv->context), toRef(priv->prototype.get()), toRef(priv->parentClass->priv->prototype.get()));
142-
}
143-
144306
static void jsc_class_class_init(JSCClassClass* klass)
145307
{
146308
GObjectClass* objClass = G_OBJECT_CLASS(klass);
147-
objClass->constructed = jscClassConstructed;
148309
objClass->dispose = jscClassDispose;
149310
objClass->get_property = jscClassGetProperty;
150311
objClass->set_property = jscClassSetProperty;
@@ -192,10 +353,125 @@ static void jsc_class_class_init(JSCClassClass* klass)
192353
static_cast<GParamFlags>(WEBKIT_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)));
193354
}
194355

195-
GRefPtr<JSCClass> jscClassCreate(JSCContext* context, const char* name, JSCClass* parentClass, GDestroyNotify destroyFunction)
356+
/**
357+
* JSCClassGetPropertyFunction:
358+
* @jsc_class: a #JSCClass
359+
* @context: a #JSCContext
360+
* @instance: the @jsc_class instance
361+
* @name: the property name
362+
*
363+
* The type of get_property in #JSCClassVTable. This is only required when you need to handle
364+
* external properties not added to the prototype.
365+
*
366+
* Returns: (transfer full) (nullable): a #JSCValue or %NULL to forward the request to
367+
* the parent class or prototype chain
368+
*/
369+
370+
/**
371+
* JSCClassSetPropertyFunction:
372+
* @jsc_class: a #JSCClass
373+
* @context: a #JSCContext
374+
* @instance: the @jsc_class instance
375+
* @name: the property name
376+
* @value: the #JSCValue to set
377+
*
378+
* The type of set_property in #JSCClassVTable. This is only required when you need to handle
379+
* external properties not added to the prototype.
380+
*
381+
* Returns: %TRUE if handled or %FALSE to forward the request to the parent class or prototype chain.
382+
*/
383+
384+
/**
385+
* JSCClassHasPropertyFunction:
386+
* @jsc_class: a #JSCClass
387+
* @context: a #JSCContext
388+
* @instance: the @jsc_class instance
389+
* @name: the property name
390+
*
391+
* The type of has_property in #JSCClassVTable. This is only required when you need to handle
392+
* external properties not added to the prototype.
393+
*
394+
* Returns: %TRUE if @instance has a property with @name or %FALSE to forward the request
395+
* to the parent class or prototype chain.
396+
*/
397+
398+
/**
399+
* JSCClassDeletePropertyFunction:
400+
* @jsc_class: a #JSCClass
401+
* @context: a #JSCContext
402+
* @instance: the @jsc_class instance
403+
* @name: the property name
404+
*
405+
* The type of delete_property in #JSCClassVTable. This is only required when you need to handle
406+
* external properties not added to the prototype.
407+
*
408+
* Returns: %TRUE if handled or %FALSE to to forward the request to the parent class or prototype chain.
409+
*/
410+
411+
/**
412+
* JSCClassEnumeratePropertiesFunction:
413+
* @jsc_class: a #JSCClass
414+
* @context: a #JSCContext
415+
* @instance: the @jsc_class instance
416+
*
417+
* The type of enumerate_properties in #JSCClassVTable. This is only required when you need to handle
418+
* external properties not added to the prototype.
419+
*
420+
* Returns: (array zero-terminated=1) (transfer full) (nullable): a %NULL-terminated array of strings
421+
* containing the property names, or %NULL if @instance doesn't have enumerable properties.
422+
*/
423+
424+
/**
425+
* JSCClassVTable:
426+
* @get_property: a #JSCClassGetPropertyFunction for getting a property.
427+
* @set_property: a #JSCClassSetPropertyFunction for setting a property.
428+
* @has_property: a #JSCClassHasPropertyFunction for querying a property.
429+
* @delete_property: a #JSCClassDeletePropertyFunction for deleting a property.
430+
* @enumerate_properties: a #JSCClassEnumeratePropertiesFunction for enumerating properties.
431+
*
432+
* Virtual table for a JSCClass. This can be optionally used when registering a #JSCClass in a #JSCContext
433+
* to provide a custom implementation for the class. All virtual functions are optional and can be set to
434+
* %NULL to fallback to the default implementation.
435+
*/
436+
437+
GRefPtr<JSCClass> jscClassCreate(JSCContext* context, const char* name, JSCClass* parentClass, JSCClassVTable* vtable, GDestroyNotify destroyFunction)
196438
{
197439
GRefPtr<JSCClass> jscClass = adoptGRef(JSC_CLASS(g_object_new(JSC_TYPE_CLASS, "context", context, "name", name, "parent", parentClass, nullptr)));
198-
jscClass->priv->destroyFunction = destroyFunction;
440+
441+
JSCClassPrivate* priv = jscClass->priv;
442+
priv->vtable = vtable;
443+
priv->destroyFunction = destroyFunction;
444+
445+
JSClassDefinition definition = kJSClassDefinitionEmpty;
446+
definition.className = priv->name.data();
447+
448+
#define SET_IMPL_IF_NEEDED(definitionFunc, vtableFunc) \
449+
for (auto* klass = jscClass.get(); klass; klass = klass->priv->parentClass) { \
450+
if (klass->priv->vtable && klass->priv->vtable->vtableFunc) { \
451+
definition.definitionFunc = definitionFunc; \
452+
break; \
453+
} \
454+
}
455+
456+
SET_IMPL_IF_NEEDED(getProperty, get_property);
457+
SET_IMPL_IF_NEEDED(setProperty, set_property);
458+
SET_IMPL_IF_NEEDED(hasProperty, has_property);
459+
SET_IMPL_IF_NEEDED(deleteProperty, delete_property);
460+
SET_IMPL_IF_NEEDED(getPropertyNames, enumerate_properties);
461+
462+
#undef SET_IMPL_IF_NEEDED
463+
464+
priv->jsClass = JSClassCreate(&definition);
465+
466+
GUniquePtr<char> prototypeName(g_strdup_printf("%sPrototype", priv->name.data()));
467+
JSClassDefinition prototypeDefinition = kJSClassDefinitionEmpty;
468+
prototypeDefinition.className = prototypeName.get();
469+
JSClassRef prototypeClass = JSClassCreate(&prototypeDefinition);
470+
priv->prototype = jscContextGetOrCreateJSWrapper(priv->context, prototypeClass);
471+
JSClassRelease(prototypeClass);
472+
473+
if (priv->parentClass)
474+
JSObjectSetPrototype(jscContextGetJSContext(priv->context), toRef(priv->prototype.get()), toRef(priv->parentClass->priv->prototype.get()));
199475
return jscClass;
200476
}
201477

0 commit comments

Comments
 (0)