<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[230753] trunk</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://trac.webkit.org/projects/webkit/changeset/230753">230753</a></dd>
<dt>Author</dt> <dd>carlosgc@webkit.org</dd>
<dt>Date</dt> <dd>2018-04-17 23:51:33 -0700 (Tue, 17 Apr 2018)</dd>
</dl>

<h3>Log Message</h3>
<pre>[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):</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkSourceJavaScriptCoreAPIglibJSCClasscpp">trunk/Source/JavaScriptCore/API/glib/JSCClass.cpp</a></li>
<li><a href="#trunkSourceJavaScriptCoreAPIglibJSCClassh">trunk/Source/JavaScriptCore/API/glib/JSCClass.h</a></li>
<li><a href="#trunkSourceJavaScriptCoreAPIglibJSCClassPrivateh">trunk/Source/JavaScriptCore/API/glib/JSCClassPrivate.h</a></li>
<li><a href="#trunkSourceJavaScriptCoreAPIglibJSCContextcpp">trunk/Source/JavaScriptCore/API/glib/JSCContext.cpp</a></li>
<li><a href="#trunkSourceJavaScriptCoreAPIglibJSCContexth">trunk/Source/JavaScriptCore/API/glib/JSCContext.h</a></li>
<li><a href="#trunkSourceJavaScriptCoreAPIglibJSCContextPrivateh">trunk/Source/JavaScriptCore/API/glib/JSCContextPrivate.h</a></li>
<li><a href="#trunkSourceJavaScriptCoreAPIglibJSCWrapperMapcpp">trunk/Source/JavaScriptCore/API/glib/JSCWrapperMap.cpp</a></li>
<li><a href="#trunkSourceJavaScriptCoreAPIglibJSCWrapperMaph">trunk/Source/JavaScriptCore/API/glib/JSCWrapperMap.h</a></li>
<li><a href="#trunkSourceJavaScriptCoreAPIglibdocsjscglib40sectionstxt">trunk/Source/JavaScriptCore/API/glib/docs/jsc-glib-4.0-sections.txt</a></li>
<li><a href="#trunkSourceJavaScriptCoreChangeLog">trunk/Source/JavaScriptCore/ChangeLog</a></li>
<li><a href="#trunkToolsChangeLog">trunk/Tools/ChangeLog</a></li>
<li><a href="#trunkToolsTestWebKitAPITestsJavaScriptCoreglibTestJSCcpp">trunk/Tools/TestWebKitAPI/Tests/JavaScriptCore/glib/TestJSC.cpp</a></li>
<li><a href="#trunkToolsTestWebKitAPITestsWebKitGLibWebProcessTestcpp">trunk/Tools/TestWebKitAPI/Tests/WebKitGLib/WebProcessTest.cpp</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunkSourceJavaScriptCoreAPIglibJSCClasscpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/JavaScriptCore/API/glib/JSCClass.cpp (230752 => 230753)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/JavaScriptCore/API/glib/JSCClass.cpp        2018-04-18 06:03:06 UTC (rev 230752)
+++ trunk/Source/JavaScriptCore/API/glib/JSCClass.cpp   2018-04-18 06:51:33 UTC (rev 230753)
</span><span class="lines">@@ -20,11 +20,16 @@
</span><span class="cx"> #include "config.h"
</span><span class="cx"> #include "JSCClass.h"
</span><span class="cx"> 
</span><ins>+#include "APICast.h"
+#include "JSAPIWrapperObject.h"
</ins><span class="cx"> #include "JSCCallbackFunction.h"
</span><span class="cx"> #include "JSCClassPrivate.h"
</span><span class="cx"> #include "JSCContextPrivate.h"
</span><ins>+#include "JSCExceptionPrivate.h"
</ins><span class="cx"> #include "JSCInlines.h"
</span><span class="cx"> #include "JSCValuePrivate.h"
</span><ins>+#include "JSCallbackObject.h"
+#include "JSRetainPtr.h"
</ins><span class="cx"> #include <wtf/glib/GUniquePtr.h>
</span><span class="cx"> #include <wtf/glib/WTFGType.h>
</span><span class="cx"> 
</span><span class="lines">@@ -53,6 +58,7 @@
</span><span class="cx">     JSCContext* context;
</span><span class="cx">     CString name;
</span><span class="cx">     JSClassRef jsClass;
</span><ins>+    JSCClassVTable* vtable;
</ins><span class="cx">     GDestroyNotify destroyFunction;
</span><span class="cx">     JSCClass* parentClass;
</span><span class="cx">     JSC::Weak<JSC::JSObject> prototype;
</span><span class="lines">@@ -71,6 +77,182 @@
</span><span class="cx"> 
</span><span class="cx"> WEBKIT_DEFINE_TYPE(JSCClass, jsc_class, G_TYPE_OBJECT)
</span><span class="cx"> 
</span><ins>+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());
+                }
+            }
+        }
+    }
+}
+
</ins><span class="cx"> static void jscClassGetProperty(GObject* object, guint propID, GValue* value, GParamSpec* paramSpec)
</span><span class="cx"> {
</span><span class="cx">     JSCClass* jscClass = JSC_CLASS(object);
</span><span class="lines">@@ -121,30 +303,9 @@
</span><span class="cx">     G_OBJECT_CLASS(jsc_class_parent_class)->dispose(object);
</span><span class="cx"> }
</span><span class="cx"> 
</span><del>-static void jscClassConstructed(GObject* object)
-{
-    G_OBJECT_CLASS(jsc_class_parent_class)->constructed(object);
-
-    JSCClassPrivate* priv = JSC_CLASS(object)->priv;
-    JSClassDefinition definition = kJSClassDefinitionEmpty;
-    definition.className = priv->name.data();
-    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()));
-}
-
</del><span class="cx"> static void jsc_class_class_init(JSCClassClass* klass)
</span><span class="cx"> {
</span><span class="cx">     GObjectClass* objClass = G_OBJECT_CLASS(klass);
</span><del>-    objClass->constructed = jscClassConstructed;
</del><span class="cx">     objClass->dispose = jscClassDispose;
</span><span class="cx">     objClass->get_property = jscClassGetProperty;
</span><span class="cx">     objClass->set_property = jscClassSetProperty;
</span><span class="lines">@@ -192,10 +353,125 @@
</span><span class="cx">             static_cast<GParamFlags>(WEBKIT_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)));
</span><span class="cx"> }
</span><span class="cx"> 
</span><del>-GRefPtr<JSCClass> jscClassCreate(JSCContext* context, const char* name, JSCClass* parentClass, GDestroyNotify destroyFunction)
</del><ins>+/**
+ * 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)
</ins><span class="cx"> {
</span><span class="cx">     GRefPtr<JSCClass> jscClass = adoptGRef(JSC_CLASS(g_object_new(JSC_TYPE_CLASS, "context", context, "name", name, "parent", parentClass, nullptr)));
</span><del>-    jscClass->priv->destroyFunction = destroyFunction;
</del><ins>+
+    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()));
</ins><span class="cx">     return jscClass;
</span><span class="cx"> }
</span><span class="cx"> 
</span></span></pre></div>
<a id="trunkSourceJavaScriptCoreAPIglibJSCClassh"></a>
<div class="modfile"><h4>Modified: trunk/Source/JavaScriptCore/API/glib/JSCClass.h (230752 => 230753)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/JavaScriptCore/API/glib/JSCClass.h  2018-04-18 06:03:06 UTC (rev 230752)
+++ trunk/Source/JavaScriptCore/API/glib/JSCClass.h     2018-04-18 06:51:33 UTC (rev 230753)
</span><span class="lines">@@ -39,6 +39,42 @@
</span><span class="cx"> 
</span><span class="cx"> typedef struct _JSCContext JSCContext;
</span><span class="cx"> 
</span><ins>+typedef JSCValue *(*JSCClassGetPropertyFunction)        (JSCClass   *jsc_class,
+                                                         JSCContext *context,
+                                                         gpointer    instance,
+                                                         const char *name);
+typedef gboolean (*JSCClassSetPropertyFunction)         (JSCClass   *jsc_class,
+                                                         JSCContext *context,
+                                                         gpointer    instance,
+                                                         const char *name,
+                                                         JSCValue   *value);
+typedef gboolean (*JSCClassHasPropertyFunction)         (JSCClass   *jsc_class,
+                                                         JSCContext *context,
+                                                         gpointer    instance,
+                                                         const char *name);
+typedef gboolean (*JSCClassDeletePropertyFunction)      (JSCClass   *jsc_class,
+                                                         JSCContext *context,
+                                                         gpointer    instance,
+                                                         const char *name);
+typedef gchar  **(*JSCClassEnumeratePropertiesFunction) (JSCClass   *jsc_class,
+                                                         JSCContext *context,
+                                                         gpointer    instance);
+
+
+typedef struct {
+    JSCClassGetPropertyFunction get_property;
+    JSCClassSetPropertyFunction set_property;
+    JSCClassHasPropertyFunction has_property;
+    JSCClassDeletePropertyFunction delete_property;
+    JSCClassEnumeratePropertiesFunction enumerate_properties;
+
+    /*< private >*/
+    void (*_jsc_reserved0) (void);
+    void (*_jsc_reserved1) (void);
+    void (*_jsc_reserved2) (void);
+    void (*_jsc_reserved3) (void);
+} JSCClassVTable;
+
</ins><span class="cx"> JSC_API GType
</span><span class="cx"> jsc_class_get_type        (void);
</span><span class="cx"> 
</span></span></pre></div>
<a id="trunkSourceJavaScriptCoreAPIglibJSCClassPrivateh"></a>
<div class="modfile"><h4>Modified: trunk/Source/JavaScriptCore/API/glib/JSCClassPrivate.h (230752 => 230753)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/JavaScriptCore/API/glib/JSCClassPrivate.h   2018-04-18 06:03:06 UTC (rev 230752)
+++ trunk/Source/JavaScriptCore/API/glib/JSCClassPrivate.h      2018-04-18 06:51:33 UTC (rev 230753)
</span><span class="lines">@@ -25,7 +25,7 @@
</span><span class="cx"> #include "JSCValue.h"
</span><span class="cx"> #include <wtf/glib/GRefPtr.h>
</span><span class="cx"> 
</span><del>-GRefPtr<JSCClass> jscClassCreate(JSCContext*, const char*, JSCClass*, GDestroyNotify);
</del><ins>+GRefPtr<JSCClass> jscClassCreate(JSCContext*, const char*, JSCClass*, JSCClassVTable*, GDestroyNotify);
</ins><span class="cx"> JSClassRef jscClassGetJSClass(JSCClass*);
</span><span class="cx"> JSC::JSObject* jscClassGetOrCreateJSWrapper(JSCClass*, gpointer);
</span><span class="cx"> void jscClassInvalidate(JSCClass*);
</span></span></pre></div>
<a id="trunkSourceJavaScriptCoreAPIglibJSCContextcpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/JavaScriptCore/API/glib/JSCContext.cpp (230752 => 230753)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/JavaScriptCore/API/glib/JSCContext.cpp      2018-04-18 06:03:06 UTC (rev 230752)
+++ trunk/Source/JavaScriptCore/API/glib/JSCContext.cpp 2018-04-18 06:51:33 UTC (rev 230753)
</span><span class="lines">@@ -240,6 +240,11 @@
</span><span class="cx">     return wrapperMap(context).wrappedObject(context->priv->jsContext.get(), jsObject);
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+JSCClass* jscContextGetRegisteredClass(JSCContext* context, JSClassRef jsClass)
+{
+    return wrapperMap(context).registeredClass(jsClass);
+}
+
</ins><span class="cx"> CallbackData jscContextPushCallback(JSCContext* context, JSValueRef calleeValue, JSValueRef thisValue, size_t argumentCount, const JSValueRef* arguments)
</span><span class="cx"> {
</span><span class="cx">     Thread& thread = Thread::current();
</span><span class="lines">@@ -762,23 +767,26 @@
</span><span class="cx">  * jsc_context_register_class:
</span><span class="cx">  * @context: a #JSCContext
</span><span class="cx">  * @name: the class name
</span><del>- * @parent_class: (nullable): a #JSCClass
</del><ins>+ * @parent_class: (nullable): a #JSCClass or %NULL
+ * @vtable: (nullable): an optional #JSCClassVTable or %NULL
</ins><span class="cx">  * @destroy_notify: (nullable): a destroy notifier for class instances
</span><span class="cx">  *
</span><span class="cx">  * Register a custom class in @context using the given @name. If the new class inherits from
</span><span class="cx">  * another #JSCClass, the parent should be passed as @parent_class, otherwise %NULL should be
</span><del>- * used. When an instance of the #JSCClass is cleared in the context, @destroy_notify is
- * called with the instance as parameter.
</del><ins>+ * used. The optional @vtable parameter allows to provide a custom implementation for handling
+ * the class, for example, to handle external properties not added to the prototype.
+ * When an instance of the #JSCClass is cleared in the context, @destroy_notify is called with
+ * the instance as parameter.
</ins><span class="cx">  *
</span><span class="cx">  * Returns: (transfer none): a #JSCClass
</span><span class="cx">  */
</span><del>-JSCClass* jsc_context_register_class(JSCContext* context, const char* name, JSCClass* parentClass, GDestroyNotify destroyFunction)
</del><ins>+JSCClass* jsc_context_register_class(JSCContext* context, const char* name, JSCClass* parentClass, JSCClassVTable* vtable, GDestroyNotify destroyFunction)
</ins><span class="cx"> {
</span><span class="cx">     g_return_val_if_fail(JSC_IS_CONTEXT(context), nullptr);
</span><span class="cx">     g_return_val_if_fail(name, nullptr);
</span><span class="cx">     g_return_val_if_fail(!parentClass || JSC_IS_CLASS(parentClass), nullptr);
</span><span class="cx"> 
</span><del>-    auto jscClass = jscClassCreate(context, name, parentClass, destroyFunction);
</del><ins>+    auto jscClass = jscClassCreate(context, name, parentClass, vtable, destroyFunction);
</ins><span class="cx">     wrapperMap(context).registerClass(jscClass.get());
</span><span class="cx">     return jscClass.get();
</span><span class="cx"> }
</span></span></pre></div>
<a id="trunkSourceJavaScriptCoreAPIglibJSCContexth"></a>
<div class="modfile"><h4>Modified: trunk/Source/JavaScriptCore/API/glib/JSCContext.h (230752 => 230753)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/JavaScriptCore/API/glib/JSCContext.h        2018-04-18 06:03:06 UTC (rev 230752)
+++ trunk/Source/JavaScriptCore/API/glib/JSCContext.h   2018-04-18 06:51:33 UTC (rev 230753)
</span><span class="lines">@@ -126,6 +126,7 @@
</span><span class="cx"> jsc_context_register_class           (JSCContext         *context,
</span><span class="cx">                                       const char         *name,
</span><span class="cx">                                       JSCClass           *parent_class,
</span><ins>+                                      JSCClassVTable     *vtable,
</ins><span class="cx">                                       GDestroyNotify      destroy_notify);
</span><span class="cx"> 
</span><span class="cx"> G_END_DECLS
</span></span></pre></div>
<a id="trunkSourceJavaScriptCoreAPIglibJSCContextPrivateh"></a>
<div class="modfile"><h4>Modified: trunk/Source/JavaScriptCore/API/glib/JSCContextPrivate.h (230752 => 230753)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/JavaScriptCore/API/glib/JSCContextPrivate.h 2018-04-18 06:03:06 UTC (rev 230752)
+++ trunk/Source/JavaScriptCore/API/glib/JSCContextPrivate.h    2018-04-18 06:51:33 UTC (rev 230753)
</span><span class="lines">@@ -32,6 +32,7 @@
</span><span class="cx"> JSC::JSObject* jscContextGetJSWrapper(JSCContext*, gpointer);
</span><span class="cx"> JSC::JSObject* jscContextGetOrCreateJSWrapper(JSCContext*, JSClassRef, JSValueRef prototype = nullptr, gpointer = nullptr, GDestroyNotify = nullptr);
</span><span class="cx"> gpointer jscContextWrappedObject(JSCContext*, JSObjectRef);
</span><ins>+JSCClass* jscContextGetRegisteredClass(JSCContext*, JSClassRef);
</ins><span class="cx"> 
</span><span class="cx"> struct CallbackData {
</span><span class="cx">     GRefPtr<JSCContext> context;
</span></span></pre></div>
<a id="trunkSourceJavaScriptCoreAPIglibJSCWrapperMapcpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/JavaScriptCore/API/glib/JSCWrapperMap.cpp (230752 => 230753)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/JavaScriptCore/API/glib/JSCWrapperMap.cpp   2018-04-18 06:03:06 UTC (rev 230752)
+++ trunk/Source/JavaScriptCore/API/glib/JSCWrapperMap.cpp      2018-04-18 06:51:33 UTC (rev 230753)
</span><span class="lines">@@ -68,6 +68,11 @@
</span><span class="cx">     m_classMap.set(jsClass, jscClass);
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+JSCClass* WrapperMap::registeredClass(JSClassRef jsClass) const
+{
+    return m_classMap.get(jsClass);
+}
+
</ins><span class="cx"> JSObject* WrapperMap::createJSWrappper(JSGlobalContextRef jsContext, JSClassRef jsClass, JSValueRef prototype, gpointer wrappedObject, GDestroyNotify destroyFunction)
</span><span class="cx"> {
</span><span class="cx">     ASSERT(toJSGlobalObject(jsContext)->wrapperMap() == this);
</span></span></pre></div>
<a id="trunkSourceJavaScriptCoreAPIglibJSCWrapperMaph"></a>
<div class="modfile"><h4>Modified: trunk/Source/JavaScriptCore/API/glib/JSCWrapperMap.h (230752 => 230753)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/JavaScriptCore/API/glib/JSCWrapperMap.h     2018-04-18 06:03:06 UTC (rev 230752)
+++ trunk/Source/JavaScriptCore/API/glib/JSCWrapperMap.h        2018-04-18 06:51:33 UTC (rev 230753)
</span><span class="lines">@@ -45,6 +45,7 @@
</span><span class="cx">     void unwrap(JSValueRef);
</span><span class="cx"> 
</span><span class="cx">     void registerClass(JSCClass*);
</span><ins>+    JSCClass* registeredClass(JSClassRef) const;
</ins><span class="cx"> 
</span><span class="cx">     JSObject* createJSWrappper(JSGlobalContextRef, JSClassRef, JSValueRef prototype, gpointer, GDestroyNotify);
</span><span class="cx">     JSObject* jsWrapper(gpointer wrappedObject) const;
</span></span></pre></div>
<a id="trunkSourceJavaScriptCoreAPIglibdocsjscglib40sectionstxt"></a>
<div class="modfile"><h4>Modified: trunk/Source/JavaScriptCore/API/glib/docs/jsc-glib-4.0-sections.txt (230752 => 230753)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/JavaScriptCore/API/glib/docs/jsc-glib-4.0-sections.txt      2018-04-18 06:03:06 UTC (rev 230752)
+++ trunk/Source/JavaScriptCore/API/glib/docs/jsc-glib-4.0-sections.txt 2018-04-18 06:51:33 UTC (rev 230753)
</span><span class="lines">@@ -162,6 +162,12 @@
</span><span class="cx"> <FILE>JSCClass</FILE>
</span><span class="cx"> <TITLE>JSCClass</TITLE>
</span><span class="cx"> JSCClass
</span><ins>+JSCClassGetPropertyFunction
+JSCClassSetPropertyFunction
+JSCClassHasPropertyFunction
+JSCClassDeletePropertyFunction
+JSCClassEnumeratePropertiesFunction
+JSCClassVTable
</ins><span class="cx"> jsc_class_get_name
</span><span class="cx"> jsc_class_get_parent
</span><span class="cx"> jsc_class_add_constructor
</span></span></pre></div>
<a id="trunkSourceJavaScriptCoreChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/Source/JavaScriptCore/ChangeLog (230752 => 230753)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/JavaScriptCore/ChangeLog    2018-04-18 06:03:06 UTC (rev 230752)
+++ trunk/Source/JavaScriptCore/ChangeLog       2018-04-18 06:51:33 UTC (rev 230753)
</span><span class="lines">@@ -1,3 +1,38 @@
</span><ins>+2018-04-17  Carlos Garcia Campos  <cgarcia@igalia.com>
+
+        [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.
+
+        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.
+
</ins><span class="cx"> 2018-04-17  Mark Lam  <mark.lam@apple.com>
</span><span class="cx"> 
</span><span class="cx">         Templatize CodePtr/Refs/FunctionPtrs with PtrTags.
</span></span></pre></div>
<a id="trunkToolsChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/Tools/ChangeLog (230752 => 230753)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Tools/ChangeLog    2018-04-18 06:03:06 UTC (rev 230752)
+++ trunk/Tools/ChangeLog       2018-04-18 06:51:33 UTC (rev 230753)
</span><span class="lines">@@ -1,3 +1,23 @@
</span><ins>+2018-04-17  Carlos Garcia Campos  <cgarcia@igalia.com>
+
+        [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.
+
+        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):
+
</ins><span class="cx"> 2018-04-17  Wenson Hsieh  <wenson_hsieh@apple.com>
</span><span class="cx"> 
</span><span class="cx">         [Extra zoom mode] Programmatically changing focus when an element already has focus is a confusing experience
</span></span></pre></div>
<a id="trunkToolsTestWebKitAPITestsJavaScriptCoreglibTestJSCcpp"></a>
<div class="modfile"><h4>Modified: trunk/Tools/TestWebKitAPI/Tests/JavaScriptCore/glib/TestJSC.cpp (230752 => 230753)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Tools/TestWebKitAPI/Tests/JavaScriptCore/glib/TestJSC.cpp  2018-04-18 06:03:06 UTC (rev 230752)
+++ trunk/Tools/TestWebKitAPI/Tests/JavaScriptCore/glib/TestJSC.cpp     2018-04-18 06:51:33 UTC (rev 230753)
</span><span class="lines">@@ -944,12 +944,15 @@
</span><span class="cx"> typedef struct _Foo Foo;
</span><span class="cx"> struct _Foo {
</span><span class="cx">     int foo;
</span><ins>+    HashMap<CString, int> properties;
</ins><span class="cx">     Foo* sibling;
</span><span class="cx"> };
</span><span class="cx"> 
</span><span class="cx"> static Foo* fooCreate()
</span><span class="cx"> {
</span><del>-    return g_new0(Foo, 1);
</del><ins>+    Foo* foo = g_new0(Foo, 1);
+    new (foo) Foo();
+    return foo;
</ins><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> static Foo* fooCreateWithFoo(int value)
</span><span class="lines">@@ -961,6 +964,7 @@
</span><span class="cx"> 
</span><span class="cx"> static void fooFree(Foo* foo)
</span><span class="cx"> {
</span><ins>+    foo->~Foo();
</ins><span class="cx">     g_free(foo);
</span><span class="cx"> }
</span><span class="cx"> 
</span><span class="lines">@@ -989,6 +993,19 @@
</span><span class="cx">     foo->foo *= multiplier;
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+static int fooGetProperty(Foo* foo, const char* name)
+{
+    auto addResult = foo->properties.add(name, 0);
+    return addResult.iterator->value;
+}
+
+static void fooSetProperty(Foo* foo, const char* name, int value)
+{
+    auto addResult = foo->properties.add(name, value);
+    if (!addResult.isNewEntry)
+        addResult.iterator->value = value;
+}
+
</ins><span class="cx"> struct PromiseData {
</span><span class="cx">     Foo* foo;
</span><span class="cx">     int multiplier;
</span><span class="lines">@@ -1040,6 +1057,92 @@
</span><span class="cx">     return g_new0(Baz, 1);
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+static JSCClassVTable fooVTable = {
+    // get_property
+    [](JSCClass* jscClass, JSCContext* context, gpointer instance, const char* name) -> JSCValue* {
+        auto* checker = static_cast<LeakChecker*>(g_object_get_data(G_OBJECT(jscClass), "leak-checker"));
+        checker->watch(context);
+
+        if (!g_str_has_prefix(name, "prop_"))
+            return nullptr;
+
+        if (!g_strcmp0(name, "prop_throw_on_get")) {
+            jsc_context_throw(context, "Invalid property");
+            return jsc_value_new_undefined(context);
+        }
+
+        auto* foo = static_cast<Foo*>(instance);
+        auto* returnValue = jsc_value_new_number(context, fooGetProperty(foo, name));
+        checker->watch(returnValue);
+        return returnValue;
+    },
+    // set_property
+    [](JSCClass* jscClass, JSCContext* context, gpointer instance, const char* name, JSCValue* value) -> gboolean {
+        auto* checker = static_cast<LeakChecker*>(g_object_get_data(G_OBJECT(jscClass), "leak-checker"));
+        checker->watch(context);
+        checker->watch(value);
+
+        if (!g_str_has_prefix(name, "prop_"))
+            return FALSE;
+
+        if (!jsc_value_is_number(value)) {
+            jsc_context_throw(context, "Invalid value set: only numbers are allowed");
+            return TRUE;
+        }
+
+        auto* foo = static_cast<Foo*>(instance);
+        fooSetProperty(foo, name, jsc_value_to_int32(value));
+        return true;
+    },
+    // has_property
+    [](JSCClass* jscClass, JSCContext* context, gpointer instance, const char* name) -> gboolean {
+        auto* checker = static_cast<LeakChecker*>(g_object_get_data(G_OBJECT(jscClass), "leak-checker"));
+        checker->watch(context);
+        return g_str_has_prefix(name, "prop_");
+    },
+    // delete_property
+    [](JSCClass* jscClass, JSCContext* context, gpointer instance, const char* name) -> gboolean {
+        auto* checker = static_cast<LeakChecker*>(g_object_get_data(G_OBJECT(jscClass), "leak-checker"));
+        checker->watch(context);
+
+        if (!g_strcmp0(name, "prop_cant_delete"))
+            return FALSE;
+
+        if (!g_strcmp0(name, "prop_throw_on_delete")) {
+            jsc_context_throw(context, "Invalid property");
+            return TRUE;
+        }
+
+        auto* foo = static_cast<Foo*>(instance);
+        if (!foo->properties.contains(name))
+            return FALSE;
+
+        foo->properties.remove(name);
+        return TRUE;
+    },
+    // enumerate_properties
+    [](JSCClass* jscClass, JSCContext* context, gpointer instance) -> char** {
+        auto* checker = static_cast<LeakChecker*>(g_object_get_data(G_OBJECT(jscClass), "leak-checker"));
+        checker->watch(context);
+
+        auto* foo = static_cast<Foo*>(instance);
+        GRefPtr<GPtrArray> properties = adoptGRef(g_ptr_array_new_with_free_func(g_free));
+        Vector<CString> names = copyToVector(foo->properties.keys());
+        std::sort(names.begin(), names.end());
+        for (const auto& name : names) {
+            if (g_str_has_prefix(name.data(), "prop_enum_"))
+                g_ptr_array_add(properties.get(), g_strdup(name.data()));
+        }
+        if (!properties->len)
+            return nullptr;
+
+        g_ptr_array_add(properties.get(), nullptr);
+        return reinterpret_cast<char**>(g_ptr_array_free(properties.leakRef(), FALSE));
+    },
+    // padding
+    nullptr, nullptr, nullptr, nullptr
+};
+
</ins><span class="cx"> static void testJSCClass()
</span><span class="cx"> {
</span><span class="cx">     {
</span><span class="lines">@@ -1048,7 +1151,7 @@
</span><span class="cx">         checker.watch(context.get());
</span><span class="cx">         ExceptionHandler exceptionHandler(context.get());
</span><span class="cx"> 
</span><del>-        JSCClass* jscClass = jsc_context_register_class(context.get(), "Foo", nullptr, reinterpret_cast<GDestroyNotify>(fooFree));
</del><ins>+        JSCClass* jscClass = jsc_context_register_class(context.get(), "Foo", nullptr, nullptr, reinterpret_cast<GDestroyNotify>(fooFree));
</ins><span class="cx">         checker.watch(jscClass);
</span><span class="cx">         g_assert_false(jsc_class_get_parent(jscClass));
</span><span class="cx"> 
</span><span class="lines">@@ -1139,7 +1242,7 @@
</span><span class="cx">         g_assert_true(jsc_value_is_number(value.get()));
</span><span class="cx">         g_assert_cmpint(jsc_value_to_int32(value.get()), ==, 52);
</span><span class="cx"> 
</span><del>-        JSCClass* otherClass = jsc_context_register_class(context.get(), "Baz", nullptr, g_free);
</del><ins>+        JSCClass* otherClass = jsc_context_register_class(context.get(), "Baz", nullptr, nullptr, g_free);
</ins><span class="cx">         checker.watch(otherClass);
</span><span class="cx">         g_assert_false(jsc_class_get_parent(otherClass));
</span><span class="cx"> 
</span><span class="lines">@@ -1164,7 +1267,7 @@
</span><span class="cx">         checker.watch(context.get());
</span><span class="cx">         ExceptionHandler exceptionHandler(context.get());
</span><span class="cx"> 
</span><del>-        JSCClass* jscClass = jsc_context_register_class(context.get(), "Foo", nullptr, reinterpret_cast<GDestroyNotify>(fooFree));
</del><ins>+        JSCClass* jscClass = jsc_context_register_class(context.get(), "Foo", nullptr, nullptr, reinterpret_cast<GDestroyNotify>(fooFree));
</ins><span class="cx">         checker.watch(jscClass);
</span><span class="cx"> 
</span><span class="cx">         GRefPtr<JSCValue> constructor = adoptGRef(jsc_class_add_constructor(jscClass, nullptr, G_CALLBACK(fooCreate), nullptr, nullptr, G_TYPE_POINTER, 0, G_TYPE_NONE));
</span><span class="lines">@@ -1205,7 +1308,7 @@
</span><span class="cx">         checker.watch(context.get());
</span><span class="cx">         ExceptionHandler exceptionHandler(context.get());
</span><span class="cx"> 
</span><del>-        JSCClass* jscClass = jsc_context_register_class(context.get(), "Foo", nullptr, reinterpret_cast<GDestroyNotify>(fooFree));
</del><ins>+        JSCClass* jscClass = jsc_context_register_class(context.get(), "Foo", nullptr, nullptr, reinterpret_cast<GDestroyNotify>(fooFree));
</ins><span class="cx">         checker.watch(jscClass);
</span><span class="cx"> 
</span><span class="cx">         GRefPtr<JSCValue> constructor = adoptGRef(jsc_class_add_constructor(jscClass, nullptr, G_CALLBACK(fooCreate), nullptr, nullptr, G_TYPE_POINTER, 0, G_TYPE_NONE));
</span><span class="lines">@@ -1240,7 +1343,7 @@
</span><span class="cx">         g_assert_true(jsc_value_is_number(result.get()));
</span><span class="cx">         g_assert_cmpint(jsc_value_to_int32(result.get()), ==, 42);
</span><span class="cx"> 
</span><del>-        JSCClass* bazClass = jsc_context_register_class(context.get(), "Baz", nullptr, g_free);
</del><ins>+        JSCClass* bazClass = jsc_context_register_class(context.get(), "Baz", nullptr, nullptr, g_free);
</ins><span class="cx">         checker.watch(bazClass);
</span><span class="cx"> 
</span><span class="cx">         GRefPtr<JSCValue> constructor2 = adoptGRef(jsc_class_add_constructor(bazClass, nullptr, G_CALLBACK(bazCreate), nullptr, nullptr, G_TYPE_POINTER, 0, G_TYPE_NONE));
</span><span class="lines">@@ -1285,7 +1388,7 @@
</span><span class="cx">         checker.watch(context.get());
</span><span class="cx">         ExceptionHandler exceptionHandler(context.get());
</span><span class="cx"> 
</span><del>-        JSCClass* jscClass = jsc_context_register_class(context.get(), "Foo", nullptr, reinterpret_cast<GDestroyNotify>(fooFree));
</del><ins>+        JSCClass* jscClass = jsc_context_register_class(context.get(), "Foo", nullptr, nullptr, reinterpret_cast<GDestroyNotify>(fooFree));
</ins><span class="cx">         checker.watch(jscClass);
</span><span class="cx"> 
</span><span class="cx">         GRefPtr<JSCValue> constructor = adoptGRef(jsc_class_add_constructor(jscClass, nullptr, G_CALLBACK(fooCreate), nullptr, nullptr, G_TYPE_POINTER, 0, G_TYPE_NONE));
</span><span class="lines">@@ -1327,7 +1430,7 @@
</span><span class="cx">         checker.watch(context.get());
</span><span class="cx">         ExceptionHandler exceptionHandler(context.get());
</span><span class="cx"> 
</span><del>-        JSCClass* jscClass = jsc_context_register_class(context.get(), "Foo", nullptr, reinterpret_cast<GDestroyNotify>(fooFree));
</del><ins>+        JSCClass* jscClass = jsc_context_register_class(context.get(), "Foo", nullptr, nullptr, reinterpret_cast<GDestroyNotify>(fooFree));
</ins><span class="cx">         checker.watch(jscClass);
</span><span class="cx">         g_assert_false(jsc_class_get_parent(jscClass));
</span><span class="cx"> 
</span><span class="lines">@@ -1366,7 +1469,7 @@
</span><span class="cx">         checker.watch(context.get());
</span><span class="cx">         ExceptionHandler exceptionHandler(context.get());
</span><span class="cx"> 
</span><del>-        JSCClass* jscClass = jsc_context_register_class(context.get(), "Foo", nullptr, reinterpret_cast<GDestroyNotify>(fooFree));
</del><ins>+        JSCClass* jscClass = jsc_context_register_class(context.get(), "Foo", nullptr, nullptr, reinterpret_cast<GDestroyNotify>(fooFree));
</ins><span class="cx">         checker.watch(jscClass);
</span><span class="cx">         g_assert_false(jsc_class_get_parent(jscClass));
</span><span class="cx"> 
</span><span class="lines">@@ -1394,7 +1497,7 @@
</span><span class="cx"> 
</span><span class="cx">         jsc_context_set_value(context.get(), "wk", jsNamespace.get());
</span><span class="cx"> 
</span><del>-        JSCClass* jscClass = jsc_context_register_class(context.get(), "Foo", nullptr, reinterpret_cast<GDestroyNotify>(fooFree));
</del><ins>+        JSCClass* jscClass = jsc_context_register_class(context.get(), "Foo", nullptr, nullptr, reinterpret_cast<GDestroyNotify>(fooFree));
</ins><span class="cx">         checker.watch(jscClass);
</span><span class="cx"> 
</span><span class="cx">         GRefPtr<JSCValue> constructor = adoptGRef(jsc_class_add_constructor(jscClass, nullptr, G_CALLBACK(fooCreate), nullptr, nullptr, G_TYPE_POINTER, 0, G_TYPE_NONE));
</span><span class="lines">@@ -1471,6 +1574,180 @@
</span><span class="cx">         g_assert_true(jsc_value_is_number(result.get()));
</span><span class="cx">         g_assert_cmpint(jsc_value_to_int32(result.get()), ==, 62);
</span><span class="cx">     }
</span><ins>+
+    {
+        LeakChecker checker;
+        GRefPtr<JSCContext> context = adoptGRef(jsc_context_new());
+        checker.watch(context.get());
+        ExceptionHandler exceptionHandler(context.get());
+
+        JSCClass* jscClass = jsc_context_register_class(context.get(), "Foo", nullptr, &fooVTable, reinterpret_cast<GDestroyNotify>(fooFree));
+        checker.watch(jscClass);
+        g_object_set_data(G_OBJECT(jscClass), "leak-checker", &checker);
+
+        GRefPtr<JSCValue> constructor = adoptGRef(jsc_class_add_constructor(jscClass, nullptr, G_CALLBACK(fooCreate), nullptr, nullptr, G_TYPE_POINTER, 0, G_TYPE_NONE));
+        checker.watch(constructor.get());
+        g_assert_true(jsc_value_is_constructor(constructor.get()));
+        jsc_context_set_value(context.get(), jsc_class_get_name(jscClass), constructor.get());
+        jsc_class_add_property(jscClass, "foo", G_TYPE_INT, G_CALLBACK(getFoo), G_CALLBACK(setFoo), nullptr, nullptr);
+
+        GRefPtr<JSCValue> foo = adoptGRef(jsc_context_evaluate(context.get(), "f = new Foo();", -1));
+        checker.watch(foo.get());
+        g_assert_true(jsc_value_is_object(foo.get()));
+        g_assert_true(jsc_value_object_has_property(foo.get(), "foo"));
+
+        g_assert_true(jsc_value_object_has_property(foo.get(), "prop_whatever"));
+        g_assert_false(jsc_value_object_has_property(foo.get(), "whatever_prop"));
+
+        GUniquePtr<char*> properties(jsc_value_object_enumerate_properties(foo.get()));
+        g_assert_null(properties.get());
+
+        GRefPtr<JSCValue> result = adoptGRef(jsc_context_evaluate(context.get(), "f.prop_1", -1));
+        checker.watch(result.get());
+        g_assert_true(jsc_value_is_number(result.get()));
+        g_assert_cmpuint(jsc_value_to_int32(result.get()), ==, 0);
+
+        GRefPtr<JSCValue> value = adoptGRef(jsc_value_object_get_property(foo.get(), "prop_1"));
+        checker.watch(value.get());
+        g_assert_true(value.get() == result.get());
+
+        result = adoptGRef(jsc_context_evaluate(context.get(), "f.foo", -1));
+        checker.watch(result.get());
+        g_assert_true(jsc_value_is_number(result.get()));
+        g_assert_cmpuint(jsc_value_to_int32(result.get()), ==, 0);
+
+        value = adoptGRef(jsc_value_object_get_property(foo.get(), "foo"));
+        checker.watch(value.get());
+        g_assert_true(value.get() == result.get());
+
+        result = adoptGRef(jsc_context_evaluate(context.get(), "'foo' in f.__proto__", -1));
+        checker.watch(result.get());
+        g_assert_true(jsc_value_is_boolean(result.get()));
+        g_assert_true(jsc_value_to_boolean(result.get()));
+
+        result = adoptGRef(jsc_context_evaluate(context.get(), "'foo' in f", -1));
+        checker.watch(result.get());
+        g_assert_true(jsc_value_is_boolean(result.get()));
+        g_assert_true(jsc_value_to_boolean(result.get()));
+
+        result = adoptGRef(jsc_context_evaluate(context.get(), "'prop_1' in f.__proto__", -1));
+        checker.watch(result.get());
+        g_assert_true(jsc_value_is_boolean(result.get()));
+        g_assert_false(jsc_value_to_boolean(result.get()));
+
+        result = adoptGRef(jsc_context_evaluate(context.get(), "'prop_1' in f", -1));
+        checker.watch(result.get());
+        g_assert_true(jsc_value_is_boolean(result.get()));
+        g_assert_true(jsc_value_to_boolean(result.get()));
+
+        result = adoptGRef(jsc_context_evaluate(context.get(), "f.prop_1 = 25", -1));
+        checker.watch(result.get());
+        g_assert_true(jsc_value_is_number(result.get()));
+        g_assert_cmpuint(jsc_value_to_int32(result.get()), ==, 25);
+
+        g_assert_true(jsc_value_object_delete_property(foo.get(), "prop_1"));
+        g_assert_true(jsc_value_object_has_property(foo.get(), "prop_1"));
+        value = adoptGRef(jsc_value_object_get_property(foo.get(), "prop_1"));
+        checker.watch(value.get());
+        g_assert_true(jsc_value_is_number(value.get()));
+        g_assert_cmpuint(jsc_value_to_int32(value.get()), ==, 0);
+
+        result = adoptGRef(jsc_context_evaluate(context.get(), "f.prop_cant_delete = 125", -1));
+        checker.watch(result.get());
+        g_assert_true(jsc_value_is_number(result.get()));
+        g_assert_cmpuint(jsc_value_to_int32(result.get()), ==, 125);
+        jsc_value_object_delete_property(foo.get(), "prop_cant_delete");
+        g_assert_true(jsc_value_object_has_property(foo.get(), "prop_cant_delete"));
+        value = adoptGRef(jsc_value_object_get_property(foo.get(), "prop_cant_delete"));
+        checker.watch(value.get());
+        g_assert_true(jsc_value_is_number(value.get()));
+        g_assert_cmpuint(jsc_value_to_int32(value.get()), ==, 125);
+
+        value = adoptGRef(jsc_value_new_number(context.get(), 42));
+        checker.watch(value.get());
+        jsc_value_object_set_property(foo.get(), "prop_1", value.get());
+        result = adoptGRef(jsc_context_evaluate(context.get(), "f.prop_1", -1));
+        checker.watch(result.get());
+        g_assert_true(value.get() == result.get());
+
+        result = adoptGRef(jsc_context_evaluate(context.get(), "f.prop_2 = 35", -1));
+        checker.watch(result.get());
+        g_assert_true(jsc_value_is_number(result.get()));
+        g_assert_cmpuint(jsc_value_to_int32(result.get()), ==, 35);
+
+        result = adoptGRef(jsc_context_evaluate(context.get(), "'prop_2' in f.__proto__", -1));
+        checker.watch(result.get());
+        g_assert_true(jsc_value_is_boolean(result.get()));
+        g_assert_false(jsc_value_to_boolean(result.get()));
+
+        result = adoptGRef(jsc_context_evaluate(context.get(), "'prop_2' in f", -1));
+        checker.watch(result.get());
+        g_assert_true(jsc_value_is_boolean(result.get()));
+        g_assert_true(jsc_value_to_boolean(result.get()));
+
+        result = adoptGRef(jsc_context_evaluate(context.get(), "f.prop_enum_1 = 250", -1));
+        checker.watch(result.get());
+        g_assert_true(jsc_value_is_number(result.get()));
+        g_assert_cmpuint(jsc_value_to_int32(result.get()), ==, 250);
+        result = adoptGRef(jsc_context_evaluate(context.get(), "f.prop_enum_2 = 450", -1));
+        checker.watch(result.get());
+        g_assert_true(jsc_value_is_number(result.get()));
+        g_assert_cmpuint(jsc_value_to_int32(result.get()), ==, 450);
+
+        properties.reset(jsc_value_object_enumerate_properties(foo.get()));
+        g_assert_cmpuint(g_strv_length(properties.get()), ==, 2);
+        g_assert_cmpstr(properties.get()[0], ==, "prop_enum_1");
+        g_assert_cmpstr(properties.get()[1], ==, "prop_enum_2");
+        g_assert_null(properties.get()[2]);
+
+        g_assert_null(jsc_context_get_exception(context.get()));
+        bool didThrow = false;
+        g_assert_throw_begin(exceptionHandler, didThrow);
+        result = adoptGRef(jsc_context_evaluate(context.get(), "f.prop_throw_on_get", -1));
+        checker.watch(result.get());
+        g_assert_true(jsc_value_is_undefined(result.get()));
+        g_assert_did_throw(exceptionHandler, didThrow);
+        g_assert_null(jsc_context_get_exception(context.get()));
+
+        didThrow = false;
+        g_assert_throw_begin(exceptionHandler, didThrow);
+        result = adoptGRef(jsc_context_evaluate(context.get(), "f.prop_3 = 'not a number'", -1));
+        checker.watch(result.get());
+        g_assert_true(jsc_value_is_undefined(result.get()));
+        g_assert_did_throw(exceptionHandler, didThrow);
+        g_assert_null(jsc_context_get_exception(context.get()));
+
+        didThrow = false;
+        g_assert_throw_begin(exceptionHandler, didThrow);
+        jsc_value_object_delete_property(foo.get(), "prop_throw_on_delete");
+        g_assert_did_throw(exceptionHandler, didThrow);
+        g_assert_null(jsc_context_get_exception(context.get()));
+
+        jsc_context_throw(context.get(), "Fake exception");
+        GRefPtr<JSCException> previousException = jsc_context_get_exception(context.get());
+        checker.watch(previousException.get());
+        didThrow = false;
+        g_assert_throw_begin(exceptionHandler, didThrow);
+        result = adoptGRef(jsc_context_evaluate(context.get(), "f.prop_throw_on_get", -1));
+        checker.watch(result.get());
+        g_assert_true(jsc_value_is_undefined(result.get()));
+        g_assert_did_throw(exceptionHandler, didThrow);
+        g_assert_true(jsc_context_get_exception(context.get()) == previousException.get());
+
+        didThrow = false;
+        g_assert_throw_begin(exceptionHandler, didThrow);
+        result = adoptGRef(jsc_context_evaluate(context.get(), "f.prop_3 = 'not a number'", -1));
+        checker.watch(result.get());
+        g_assert_true(jsc_value_is_undefined(result.get()));
+        g_assert_did_throw(exceptionHandler, didThrow);
+        g_assert_true(jsc_context_get_exception(context.get()) == previousException.get());
+
+        didThrow = false;
+        g_assert_throw_begin(exceptionHandler, didThrow);
+        jsc_value_object_delete_property(foo.get(), "prop_throw_on_delete");
+        g_assert_did_throw(exceptionHandler, didThrow);
+        g_assert_true(jsc_context_get_exception(context.get()) == previousException.get());
+    }
</ins><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> typedef struct {
</span><span class="lines">@@ -1498,6 +1775,28 @@
</span><span class="cx">     return bar->bar;
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+static JSCClassVTable barVTable = {
+    // get_property
+    nullptr,
+    // set_property
+    nullptr,
+    // has_property
+    nullptr,
+    // delete_property
+    nullptr,
+    // enumerate_properties
+    [](JSCClass* jscClass, JSCContext* context, gpointer instance) -> char** {
+        auto* checker = static_cast<LeakChecker*>(g_object_get_data(G_OBJECT(jscClass), "leak-checker"));
+        checker->watch(context);
+
+        auto* properties = static_cast<char**>(g_malloc0(2 * sizeof(char*)));
+        properties[0] = g_strdup("bar");
+        return properties;
+    },
+    // padding
+    nullptr, nullptr, nullptr, nullptr
+};
+
</ins><span class="cx"> static void testJSCPrototypes()
</span><span class="cx"> {
</span><span class="cx">     {
</span><span class="lines">@@ -1506,7 +1805,7 @@
</span><span class="cx">         checker.watch(context.get());
</span><span class="cx">         ExceptionHandler exceptionHandler(context.get());
</span><span class="cx"> 
</span><del>-        JSCClass* fooClass = jsc_context_register_class(context.get(), "Foo", nullptr, reinterpret_cast<GDestroyNotify>(fooFree));
</del><ins>+        JSCClass* fooClass = jsc_context_register_class(context.get(), "Foo", nullptr, nullptr, reinterpret_cast<GDestroyNotify>(fooFree));
</ins><span class="cx">         checker.watch(fooClass);
</span><span class="cx">         g_assert_false(jsc_class_get_parent(fooClass));
</span><span class="cx">         GRefPtr<JSCValue> fooConstructor = adoptGRef(jsc_class_add_constructor(fooClass, nullptr, G_CALLBACK(fooCreate), nullptr, nullptr, G_TYPE_POINTER, 0, G_TYPE_NONE));
</span><span class="lines">@@ -1516,7 +1815,7 @@
</span><span class="cx">         jsc_class_add_method(fooClass, "multiply", G_CALLBACK(multiplyFoo), nullptr, nullptr, G_TYPE_NONE, 1, G_TYPE_INT);
</span><span class="cx">         jsc_class_add_property(fooClass, "foo", G_TYPE_INT, G_CALLBACK(getFoo), G_CALLBACK(setFoo), nullptr, nullptr);
</span><span class="cx"> 
</span><del>-        JSCClass* barClass = jsc_context_register_class(context.get(), "Bar", fooClass, reinterpret_cast<GDestroyNotify>(barFree));
</del><ins>+        JSCClass* barClass = jsc_context_register_class(context.get(), "Bar", fooClass, nullptr, reinterpret_cast<GDestroyNotify>(barFree));
</ins><span class="cx">         checker.watch(barClass);
</span><span class="cx">         g_assert_true(jsc_class_get_parent(barClass) == fooClass);
</span><span class="cx">         GRefPtr<JSCValue> barConstructor = adoptGRef(jsc_class_add_constructor(barClass, nullptr, G_CALLBACK(barCreate), nullptr, nullptr, G_TYPE_POINTER, 0, G_TYPE_NONE));
</span><span class="lines">@@ -1597,6 +1896,90 @@
</span><span class="cx">         g_assert_true(jsc_value_is_number(value.get()));
</span><span class="cx">         g_assert_cmpint(jsc_value_to_int32(value.get()), ==, 84);
</span><span class="cx">     }
</span><ins>+
+    {
+        LeakChecker checker;
+        GRefPtr<JSCContext> context = adoptGRef(jsc_context_new());
+        checker.watch(context.get());
+        ExceptionHandler exceptionHandler(context.get());
+
+        JSCClass* fooClass = jsc_context_register_class(context.get(), "Foo", nullptr, &fooVTable, reinterpret_cast<GDestroyNotify>(fooFree));
+        checker.watch(fooClass);
+        g_object_set_data(G_OBJECT(fooClass), "leak-checker", &checker);
+
+        GRefPtr<JSCValue> fooConstructor = adoptGRef(jsc_class_add_constructor(fooClass, nullptr, G_CALLBACK(fooCreate), nullptr, nullptr, G_TYPE_POINTER, 0, G_TYPE_NONE));
+        checker.watch(fooConstructor.get());
+        g_assert_true(jsc_value_is_constructor(fooConstructor.get()));
+        jsc_context_set_value(context.get(), jsc_class_get_name(fooClass), fooConstructor.get());
+        jsc_class_add_property(fooClass, "foo", G_TYPE_INT, G_CALLBACK(getFoo), G_CALLBACK(setFoo), nullptr, nullptr);
+
+        JSCClass* barClass = jsc_context_register_class(context.get(), "Bar", fooClass, &barVTable, reinterpret_cast<GDestroyNotify>(barFree));
+        checker.watch(barClass);
+        g_object_set_data(G_OBJECT(barClass), "leak-checker", &checker);
+        g_assert_true(jsc_class_get_parent(barClass) == fooClass);
+        GRefPtr<JSCValue> barConstructor = adoptGRef(jsc_class_add_constructor(barClass, nullptr, G_CALLBACK(barCreate), nullptr, nullptr, G_TYPE_POINTER, 0, G_TYPE_NONE));
+        checker.watch(barConstructor.get());
+        g_assert_true(jsc_value_is_constructor(barConstructor.get()));
+        jsc_context_set_value(context.get(), jsc_class_get_name(barClass), barConstructor.get());
+        jsc_class_add_property(barClass, "bar", G_TYPE_INT, G_CALLBACK(getBar), G_CALLBACK(setBar), nullptr, nullptr);
+
+        GRefPtr<JSCValue> bar = adoptGRef(jsc_context_evaluate(context.get(), "b = new Bar();", -1));
+        checker.watch(bar.get());
+        g_assert_true(jsc_value_is_object(bar.get()));
+        g_assert_true(jsc_value_object_has_property(bar.get(), "bar"));
+        g_assert_true(jsc_value_object_has_property(bar.get(), "foo"));
+
+        g_assert_true(jsc_value_object_has_property(bar.get(), "prop_whatever"));
+        g_assert_false(jsc_value_object_has_property(bar.get(), "whatever_prop"));
+
+        GUniquePtr<char*> properties(jsc_value_object_enumerate_properties(bar.get()));
+        g_assert_cmpuint(g_strv_length(properties.get()), ==, 1);
+        g_assert_cmpstr(properties.get()[0], ==, "bar");
+        g_assert_null(properties.get()[1]);
+
+        GRefPtr<JSCValue> result = adoptGRef(jsc_context_evaluate(context.get(), "b.prop_1", -1));
+        checker.watch(result.get());
+        g_assert_true(jsc_value_is_number(result.get()));
+        g_assert_cmpuint(jsc_value_to_int32(result.get()), ==, 0);
+
+        GRefPtr<JSCValue> value = adoptGRef(jsc_value_object_get_property(bar.get(), "prop_1"));
+        checker.watch(value.get());
+        g_assert_true(value.get() == result.get());
+
+        g_assert_true(jsc_value_object_delete_property(bar.get(), "prop_1"));
+        g_assert_true(jsc_value_object_has_property(bar.get(), "prop_1"));
+        value = adoptGRef(jsc_value_object_get_property(bar.get(), "prop_1"));
+        checker.watch(value.get());
+        g_assert_true(jsc_value_is_number(value.get()));
+        g_assert_cmpuint(jsc_value_to_int32(value.get()), ==, 0);
+
+        result = adoptGRef(jsc_context_evaluate(context.get(), "b.prop_cant_delete = 125", -1));
+        checker.watch(result.get());
+        g_assert_true(jsc_value_is_number(result.get()));
+        g_assert_cmpuint(jsc_value_to_int32(result.get()), ==, 125);
+        jsc_value_object_delete_property(bar.get(), "prop_cant_delete");
+        g_assert_true(jsc_value_object_has_property(bar.get(), "prop_cant_delete"));
+        value = adoptGRef(jsc_value_object_get_property(bar.get(), "prop_cant_delete"));
+        checker.watch(value.get());
+        g_assert_true(jsc_value_is_number(value.get()));
+        g_assert_cmpuint(jsc_value_to_int32(value.get()), ==, 125);
+
+        result = adoptGRef(jsc_context_evaluate(context.get(), "b.prop_enum_1 = 250", -1));
+        checker.watch(result.get());
+        g_assert_true(jsc_value_is_number(result.get()));
+        g_assert_cmpuint(jsc_value_to_int32(result.get()), ==, 250);
+        result = adoptGRef(jsc_context_evaluate(context.get(), "b.prop_enum_2 = 450", -1));
+        checker.watch(result.get());
+        g_assert_true(jsc_value_is_number(result.get()));
+        g_assert_cmpuint(jsc_value_to_int32(result.get()), ==, 450);
+
+        properties.reset(jsc_value_object_enumerate_properties(bar.get()));
+        g_assert_cmpuint(g_strv_length(properties.get()), ==, 3);
+        g_assert_cmpstr(properties.get()[0], ==, "bar");
+        g_assert_cmpstr(properties.get()[1], ==, "prop_enum_1");
+        g_assert_cmpstr(properties.get()[2], ==, "prop_enum_2");
+        g_assert_null(properties.get()[3]);
+    }
</ins><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> static void createError()
</span><span class="lines">@@ -1796,7 +2179,7 @@
</span><span class="cx">         checker.watch(context.get());
</span><span class="cx">         ExceptionHandler exceptionHandler(context.get());
</span><span class="cx"> 
</span><del>-        JSCClass* fooClass = jsc_context_register_class(context.get(), "Foo", nullptr, reinterpret_cast<GDestroyNotify>(fooFree));
</del><ins>+        JSCClass* fooClass = jsc_context_register_class(context.get(), "Foo", nullptr, nullptr, reinterpret_cast<GDestroyNotify>(fooFree));
</ins><span class="cx">         checker.watch(fooClass);
</span><span class="cx">         g_assert_false(jsc_class_get_parent(fooClass));
</span><span class="cx">         GRefPtr<JSCValue> fooConstructor = adoptGRef(jsc_class_add_constructor(fooClass, nullptr, G_CALLBACK(fooCreate), nullptr, nullptr, G_TYPE_POINTER, 0, G_TYPE_NONE));
</span><span class="lines">@@ -1890,7 +2273,7 @@
</span><span class="cx"> 
</span><span class="cx">         s_fooWasFreed = false;
</span><span class="cx"> 
</span><del>-        JSCClass* jscClass = jsc_context_register_class(context.get(), "Foo", nullptr, reinterpret_cast<GDestroyNotify>(fooFreeAndLog));
</del><ins>+        JSCClass* jscClass = jsc_context_register_class(context.get(), "Foo", nullptr, nullptr, reinterpret_cast<GDestroyNotify>(fooFreeAndLog));
</ins><span class="cx">         checker.watch(jscClass);
</span><span class="cx">         g_assert_false(jsc_class_get_parent(jscClass));
</span><span class="cx"> 
</span><span class="lines">@@ -2108,7 +2491,7 @@
</span><span class="cx">         ExceptionHandler exceptionHandler(context.get());
</span><span class="cx"> 
</span><span class="cx">         auto thread = Thread::create("JSCVirtualMachineTest", [&] {
</span><del>-            JSCClass* jscClass = jsc_context_register_class(context.get(), "Foo", nullptr, reinterpret_cast<GDestroyNotify>(fooFreeAndLog));
</del><ins>+            JSCClass* jscClass = jsc_context_register_class(context.get(), "Foo", nullptr, nullptr, reinterpret_cast<GDestroyNotify>(fooFreeAndLog));
</ins><span class="cx">             checker.watch(jscClass);
</span><span class="cx">             g_assert_false(jsc_class_get_parent(jscClass));
</span><span class="cx"> 
</span></span></pre></div>
<a id="trunkToolsTestWebKitAPITestsWebKitGLibWebProcessTestcpp"></a>
<div class="modfile"><h4>Modified: trunk/Tools/TestWebKitAPI/Tests/WebKitGLib/WebProcessTest.cpp (230752 => 230753)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Tools/TestWebKitAPI/Tests/WebKitGLib/WebProcessTest.cpp    2018-04-18 06:03:06 UTC (rev 230752)
+++ trunk/Tools/TestWebKitAPI/Tests/WebKitGLib/WebProcessTest.cpp       2018-04-18 06:51:33 UTC (rev 230753)
</span><span class="lines">@@ -91,7 +91,7 @@
</span><span class="cx"> 
</span><span class="cx">     GRefPtr<JSCContext> context = adoptGRef(webkit_frame_get_js_context_for_script_world(frame, world));
</span><span class="cx">     WebProcessTest::assertObjectIsDeletedWhenTestFinishes(G_OBJECT(context.get()));
</span><del>-    auto* jsClass = jsc_context_register_class(context.get(), "WebProcessTestRunner", nullptr, reinterpret_cast<GDestroyNotify>(webProcessTestRunnerFinalize));
</del><ins>+    auto* jsClass = jsc_context_register_class(context.get(), "WebProcessTestRunner", nullptr, nullptr, reinterpret_cast<GDestroyNotify>(webProcessTestRunnerFinalize));
</ins><span class="cx">     WebProcessTest::assertObjectIsDeletedWhenTestFinishes(G_OBJECT(jsClass));
</span><span class="cx">     jsc_class_add_method(jsClass, "runTest", G_CALLBACK(runTest), NULL, NULL, G_TYPE_BOOLEAN, 1, G_TYPE_STRING);
</span><span class="cx">     GRefPtr<JSCValue> testRunner = adoptGRef(jsc_value_new_object(context.get(), g_object_ref(webPage), jsClass));
</span></span></pre>
</div>
</div>

</body>
</html>