[gjs/wip/gobj-kitchen-sink: 21/26] object: Make custom types have a custom JSClass



commit 7021a31d8b789d54f5a8082f5a030271d99e0dd6
Author: Giovanni Campagna <gcampagna src gnome org>
Date:   Fri Dec 16 18:38:40 2011 +0100

    object: Make custom types have a custom JSClass
    
    Use gjs_define_object_class to create a dynamic JSClass for the
    newly registered gtype, which doesn't have any introspection info.

 gi/function.c                |    7 ++
 gi/object.c                  |  141 +++++++++++++++++++++++++++---------------
 modules/overrides/GObject.js |   74 ++++++++++++++++------
 modules/overrides/Gio.js     |   17 ++++-
 4 files changed, 167 insertions(+), 72 deletions(-)
---
diff --git a/gi/function.c b/gi/function.c
index cc44d19..48f4569 100644
--- a/gi/function.c
+++ b/gi/function.c
@@ -666,6 +666,13 @@ gjs_invoke_c_function(JSContext      *context,
             GType gtype;
 
             in_arg_cvalues[0].v_pointer = gjs_g_object_from_object(context, obj);
+            if (in_arg_cvalues[0].v_pointer == NULL) {
+                /* priv == NULL (user probably forgot to chain _init).
+                 * Anyway, in this case we've thrown an exception, so just
+                 * make sure we fail. */
+                failed = TRUE;
+                goto release;
+            }
 
             gtype = g_registered_type_info_get_g_type ((GIRegisteredTypeInfo *)container);
             if (!g_type_is_a (G_TYPE_FROM_INSTANCE (in_arg_cvalues[0].v_pointer),
diff --git a/gi/object.c b/gi/object.c
index d4b141f..7535a84 100644
--- a/gi/object.c
+++ b/gi/object.c
@@ -94,6 +94,16 @@ gjs_is_custom_type_quark (void)
     return val;
 }
 
+static GQuark
+gjs_is_custom_property_quark (void)
+{
+    static GQuark val = 0;
+    if (!val)
+        val = g_quark_from_static_string ("gjs::custom-property");
+
+    return val;
+}
+
 static void
 throw_priv_is_null_error(JSContext *context)
 {
@@ -108,7 +118,8 @@ init_g_param_from_property(JSContext  *context,
                            const char *js_prop_name,
                            jsval       js_value,
                            GType       gtype,
-                           GParameter *parameter)
+                           GParameter *parameter,
+                           gboolean    constructing)
 {
     char *gname;
     GParamSpec *param_spec;
@@ -129,6 +140,13 @@ init_g_param_from_property(JSContext  *context,
         return NO_SUCH_G_PROPERTY;
     }
 
+    /* Do not set JS overridden properties through GObject, to avoid
+     * infinite recursion (but set them when constructing) */
+    if (!constructing &&
+        g_param_spec_get_qdata(param_spec, gjs_is_custom_property_quark()))
+        return NO_SUCH_G_PROPERTY;
+
+
     if ((param_spec->flags & G_PARAM_WRITABLE) == 0) {
         /* prevent setting the prop even in JS */
         gjs_throw(context, "Property %s (GObject %s) is not writable",
@@ -175,13 +193,9 @@ object_instance_get_prop(JSContext *context,
                      "Get prop '%s' hook obj %p priv %p", name, obj, priv);
 
     if (priv == NULL) {
-        /* We won't have a private until the initializer is called, so
-         * don't mark a call to _init() an error. */
-        if (!g_str_equal(name, "_init")) {
-            ret = JS_FALSE;
-            throw_priv_is_null_error (context);
-        }
-
+        /* If we reach this point, either object_instance_new_resolve
+         * did not throw (so name == "_init"), or the property actually
+         * exists and it's not something we should be concerned with */
         goto out;
     }
     if (priv->gobj == NULL) /* prototype, not an instance. */
@@ -197,6 +211,11 @@ object_instance_get_prop(JSContext *context,
         goto out;
     }
 
+    /* Do not fetch JS overridden properties from GObject, to avoid
+     * infinite recursion. */
+    if (g_param_spec_get_qdata(param, gjs_is_custom_property_quark()))
+        goto out;
+
     if ((param->flags & G_PARAM_READABLE) == 0)
         goto out;
 
@@ -242,8 +261,7 @@ object_instance_set_prop(JSContext *context,
                      "Set prop '%s' hook obj %p priv %p", name, obj, priv);
 
     if (priv == NULL) {
-        ret = JS_FALSE;  /* wrong class passed in */
-        throw_priv_is_null_error(context);
+        /* see the comment in object_instance_get_prop() on this */
         goto out;
     }
     if (priv->gobj == NULL) /* prototype, not an instance. */
@@ -252,7 +270,8 @@ object_instance_set_prop(JSContext *context,
     switch (init_g_param_from_property(context, name,
                                        *value_p,
                                        G_TYPE_FROM_INSTANCE(priv->gobj),
-                                       &param)) {
+                                       &param,
+                                       FALSE /* constructing */)) {
     case SOME_ERROR_OCCURRED:
         ret = JS_FALSE;
     case NO_SUCH_G_PROPERTY:
@@ -562,26 +581,6 @@ object_instance_props_to_g_parameters(JSContext   *context,
     if (n_gparams_p)
         *n_gparams_p = 0;
 
-    if (argc == 0)
-        return JS_TRUE;
-
-    if (!JSVAL_IS_OBJECT(argv[0])) {
-        gjs_throw(context, "argument should be a hash with props to set");
-        return JS_FALSE;
-    }
-
-    props = JSVAL_TO_OBJECT(argv[0]);
-
-    iter = JS_NewPropertyIterator(context, props);
-    if (iter == NULL) {
-        gjs_throw(context, "Failed to create property iterator for object props hash");
-        return JS_FALSE;
-    }
-
-    prop_id = JSID_VOID;
-    if (!JS_NextProperty(context, iter, &prop_id))
-        return JS_FALSE;
-
     gparams = g_array_new(/* nul term */ FALSE, /* clear */ TRUE,
                           sizeof(GParameter));
 
@@ -607,6 +606,26 @@ object_instance_props_to_g_parameters(JSContext   *context,
         g_array_append_val(gparams, gparam);
     }
 
+    if (argc == 0 || JSVAL_IS_VOID(argv[0]))
+        goto out;
+
+    if (!JSVAL_IS_OBJECT(argv[0])) {
+        gjs_throw(context, "argument should be a hash with props to set");
+        goto free_array_and_fail;
+    }
+
+    props = JSVAL_TO_OBJECT(argv[0]);
+
+    iter = JS_NewPropertyIterator(context, props);
+    if (iter == NULL) {
+        gjs_throw(context, "Failed to create property iterator for object props hash");
+        goto free_array_and_fail;
+    }
+
+    prop_id = JSID_VOID;
+    if (!JS_NextProperty(context, iter, &prop_id))
+        goto free_array_and_fail;
+
     while (!JSID_IS_VOID(prop_id)) {
         char *name;
         jsval value;
@@ -623,7 +642,8 @@ object_instance_props_to_g_parameters(JSContext   *context,
         switch (init_g_param_from_property(context, name,
                                            value,
                                            gtype,
-                                           &gparam)) {
+                                           &gparam,
+                                           TRUE /* constructing */)) {
         case NO_SUCH_G_PROPERTY:
             gjs_throw(context, "No property %s on this GObject %s",
                          name, g_type_name(gtype));
@@ -643,6 +663,7 @@ object_instance_props_to_g_parameters(JSContext   *context,
             goto free_array_and_fail;
     }
 
+ out:
     if (n_gparams_p)
         *n_gparams_p = gparams->len;
     if (gparams_p)
@@ -918,9 +939,21 @@ GJS_NATIVE_CONSTRUCTOR_DECLARE(object_instance)
 {
     GJS_NATIVE_CONSTRUCTOR_VARIABLES(object_instance)
     JSBool ret;
+    jsval initer;
+    jsval rval;
+
     GJS_NATIVE_CONSTRUCTOR_PRELUDE(object_instance);
-    ret = object_instance_init(context, &object, argc, argv);
-    GJS_NATIVE_CONSTRUCTOR_FINISH(object_instance);
+
+    if (!gjs_object_require_property(context, object, "GObject instance", "_init", &initer))
+        return JS_FALSE;
+
+    rval = JSVAL_VOID;
+    ret = gjs_call_function_value(context, object, initer, argc, argv, &rval);
+
+    if (JSVAL_IS_VOID(rval))
+        rval = OBJECT_TO_JSVAL(object);
+
+    JS_SET_RVAL(context, vp, rval);
     return ret;
 }
 
@@ -1150,7 +1183,7 @@ emit_func(JSContext *context,
     GSignalQuery signal_query;
     char *signal_name;
     GValue *instance_and_args;
-    GValue rvalue;
+    GValue rvalue = G_VALUE_INIT;
     unsigned int i;
     gboolean failed;
     jsval retval;
@@ -1325,10 +1358,14 @@ init_func (JSContext *context,
 {
     jsval *argv = JS_ARGV(context, vp);
     JSObject *obj = JS_THIS_OBJECT(context, vp);
+    JSBool ret;
 
-    JS_SET_RVAL(context, vp, JSVAL_VOID);
+    ret = object_instance_init(context, &obj, argc, argv);
+
+    if (ret)
+        JS_SET_RVAL(context, vp, OBJECT_TO_JSVAL(obj));
 
-    return object_instance_init(context, &obj, argc, argv);
+    return ret;
 }
 
 static JSPropertySpec gjs_object_instance_proto_props[] = {
@@ -1716,14 +1753,18 @@ gjs_g_object_from_object(JSContext    *context,
 
     priv = priv_from_js(context, obj);
 
-    if (priv == NULL)
+    if (priv == NULL) {
+        gjs_throw(context,
+                  "Object instance or prototype has not been properly initialized yet. "
+                  "Did you forget to chain-up from _init()?");
         return NULL;
+    }
 
     if (priv->gobj == NULL) {
         gjs_throw(context,
                   "Object is %s.%s.prototype, not an object instance - cannot convert to GObject*",
-                  g_base_info_get_namespace( (GIBaseInfo*) priv->info),
-                  g_base_info_get_name( (GIBaseInfo*) priv->info));
+                  priv->info ? g_base_info_get_namespace( (GIBaseInfo*) priv->info) : "",
+                  priv->info ? g_base_info_get_name( (GIBaseInfo*) priv->info) : g_type_name(priv->gtype));
         return NULL;
     }
 
@@ -2016,12 +2057,11 @@ gjs_register_type(JSContext *cx,
                   jsval     *vp)
 {
     jsval *argv = JS_ARGV(cx, vp);
-    jsval gtype;
     gchar *name;
-    JSObject *parent, *object;
+    JSObject *parent, *constructor;
     GType instance_type, parent_type;
     GTypeQuery query;
-    ObjectInstance *parent_priv, *priv;
+    ObjectInstance *parent_priv;
     GTypeInfo type_info = {
         0, /* class_size */
 
@@ -2040,9 +2080,8 @@ gjs_register_type(JSContext *cx,
     JS_BeginRequest(cx);
 
     if (!gjs_parse_args(cx, "register_type",
-                        "oos", argc, argv,
+                        "os", argc, argv,
                         "parent", &parent,
-                        "object", &object,
                         "name", &name))
         return JS_FALSE;
 
@@ -2067,11 +2106,11 @@ gjs_register_type(JSContext *cx,
 
     g_type_set_qdata (instance_type, gjs_is_custom_type_quark(), GINT_TO_POINTER (1));
 
-    priv = g_slice_new0(ObjectInstance);
-    priv->info = parent_priv->info;
-    priv->gtype = instance_type;
+    /* create a custom JSClass */
+    if (!gjs_define_object_class(cx, NULL, instance_type, &constructor, NULL))
+        return JS_FALSE;
 
-    JS_SetPrivate(cx, object, priv);
+    JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(constructor));
 
     JS_EndRequest(cx);
 
@@ -2129,6 +2168,8 @@ gjs_register_property(JSContext *cx,
     priv = priv_from_js(cx, obj);
     pspec = gjs_g_param_from_param(cx, pspec_js);
 
+    g_param_spec_set_qdata(pspec, gjs_is_custom_property_quark(), GINT_TO_POINTER(1));
+
     oclass = g_type_class_ref(priv->gtype);
     g_object_class_install_property(oclass, PROP_JS_HANDLED, pspec);
     g_type_class_unref(oclass);
@@ -2230,7 +2271,7 @@ gjs_define_stuff(JSContext *context,
     if (!JS_DefineFunction(context, module_obj,
                            "register_type",
                            (JSNative)gjs_register_type,
-                           3, GJS_MODULE_PROP_FLAGS))
+                           2, GJS_MODULE_PROP_FLAGS))
         return JS_FALSE;
 
     if (!JS_DefineFunction(context, module_obj,
diff --git a/modules/overrides/GObject.js b/modules/overrides/GObject.js
index dfa12bc..bedd4bb 100644
--- a/modules/overrides/GObject.js
+++ b/modules/overrides/GObject.js
@@ -29,47 +29,45 @@ const GObjectMeta = new Lang.Class({
     Extends: Lang.Class,
 
     _init: function(params) {
-        if (!params.Extends)
-            params.Extends = GObject.Object;
+        this.parent(params);
 
-        if (!this._isValidClass(params.Extends))
-            throw new TypeError('GObject.Class used with invalid base class (is ' + params.Extends.prototype + ')');
+        // retrieve all parameters and remove them from params before chaining
 
-        this.parent(params);
+        let properties = params.Properties;
+        let signals = params.Signals;
+        let ifaces = params.Implements;
 
-        Gi.register_type(params.Extends.prototype, this.prototype, params.Name);
+        delete params.Properties;
+        delete params.Signals;
+        delete params.Implements;
 
-        if (params.Properties) {
-            for (let prop in params.Properties) {
-                Gi.register_property(this.prototype, params.Properties[prop]);
+        if (properties) {
+            for (let prop in properties) {
+                Gi.register_property(this.prototype, properties[prop]);
             }
         }
 
-        if (params.Signals) {
-            for (let signalName in params.Signals) {
-                let obj = params.Signals[signalName];
+        if (signals) {
+            for (let signalName in signals) {
+                let obj = signals[signalName];
                 let flags = (obj.flags !== undefined) ? obj.flags : GObject.SignalFlags.RUN_FIRST;
                 let accumulator = (obj.accumulator !== undefined) ? obj.accumulator : GObject.AccumulatorType.NONE;
                 let rtype = (obj.return_type !== undefined) ? obj.return_type : GObject.TYPE_NONE;
                 let paramtypes = (obj.param_types !== undefined) ? obj.param_types : [];
 
                 try {
-                    obj.signal_id = Gi.signal_new(this.prototype, signal_name, flags, accumulator, rtype, paramtypes);
+                    obj.signal_id = Gi.signal_new(this.prototype, signalName, flags, accumulator, rtype, paramtypes);
                 } catch(e) {
                     throw new TypeError('Invalid signal ' + signal_name + ': ' + e.message);
                 }
             }
         }
 
-        if (params.Implements) {
-            for (let i = 0; i < params.Implements.length; i++)
+        if (ifaces) {
+            for (let i = 0; i < ifaces.length; i++)
                 Gi.add_interface(this.prototype, ifaces[i]);
         }
 
-        delete params.Properties;
-        delete params.Signals;
-        delete params.Implements;
-
         for (let prop in params) {
             let value = this.prototype[prop];
             if (typeof value === 'function') {
@@ -101,6 +99,44 @@ const GObjectMeta = new Lang.Class({
         // will return false.
         return proto == GObject.Object.prototype ||
             proto instanceof GObject.Object;
+    },
+
+    // If we want an object with a custom JSClass, we can't just
+    // use a function. We have to use a custom constructor here.
+    _construct: function(params) {
+        if (!params.Name)
+            throw new TypeError("Classes require an explicit 'Name' parameter.");
+        let name = params.Name;
+
+        let gtypename;
+        if (params.GTypeName)
+            gtypename = params.GTypeName;
+        else
+            gtypename = 'Gjs_' + params.Name;
+
+        if (!params.Extends)
+            params.Extends = GObject.Object;
+        let parent = params.Extends;
+
+        if (!this._isValidClass(parent))
+            throw new TypeError('GObject.Class used with invalid base class (is ' + parent + ')');
+
+        let newClass = Gi.register_type(parent.prototype, gtypename);
+
+        // See Class.prototype._construct in lang.js for the reasoning
+        // behind this direct __proto__ set.
+        newClass.__proto__ = this.constructor.prototype;
+        newClass.__super__ = parent;
+
+        newClass._init.apply(newClass, arguments);
+
+        Object.defineProperty(newClass.prototype, '__metaclass__',
+                              { writable: false,
+                                configurable: false,
+                                enumerable: false,
+                                value: this.constructor });
+
+        return newClass;
     }
 });
 
diff --git a/modules/overrides/Gio.js b/modules/overrides/Gio.js
index 0098832..6db07d7 100644
--- a/modules/overrides/Gio.js
+++ b/modules/overrides/Gio.js
@@ -171,9 +171,15 @@ const DBusProxyClass = new Lang.Class({
     Name: 'DBusProxyClass',
     Extends: GObject.Class,
 
-    _init: function(params) {
+    _construct: function(params) {
+	/* params.Extends cannot be set in _init(), as
+	   GObject.Class._construct needs it */
 	params.Extends = Gio.DBusProxy;
 
+	return this.parent(params);
+    },
+
+    _init: function(params) {
 	if (!params.Interface)
 	    throw new TypeError('Interface must be specified in the declaration of a DBusProxyClass');
 	if (!(params.Interface instanceof Gio.DBusInterfaceInfo))
@@ -424,6 +430,13 @@ const DBusImplementerClass = new Lang.Class({
     Name: 'DBusImplementerClass',
     Extends: Lang.Class,
 
+    _construct: function(params) {
+	/* this must be set inside _construct, not _init */
+	params.Extends = DBusImplementerBase;
+
+	return this.parent(params);
+    },
+
     _init: function(params) {
 	if (!params.Interface)
 	    throw new TypeError('Interface must be specified in the declaration of a DBusImplementerClass');
@@ -434,8 +447,6 @@ const DBusImplementerClass = new Lang.Class({
 	this.Interface = params.Interface;
 	delete params.Interface;
 
-	params.Extends = DBusImplementerBase;
-
 	this.parent(params);
     }
 });



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]