[gjs/wip/gobj-kitchen-sink: 25/26] object: Put vfuncs implementations in a separate namespace



commit f24087654f894c0527851b15a25ef901b3e095e5
Author: Jasper St. Pierre <jstpierre mecheye net>
Date:   Sat Jan 28 00:15:00 2012 -0500

    object: Put vfuncs implementations in a separate namespace
    
    Rather than depending on a prefix, "do_", to expose parent vfuncs and hook
    up automatically, introduce a new namespace, "$gobject_vfuncs", which exposes
    implementations of vfuncs.
    
    When subclassing a GObject, to have vfuncs hooked up automatically, use
    a new special property, "VFuncImpls":
    
        const MyVFuncableClass = new Lang.Class({
            Name: 'MyVFuncableClass',
            Extends: GObject.Object,
            Implements: [ Gio.Initable ],
    
            _init: function() {
                this.parent();
                this.inited = false;
            },
    
            VFuncImpls: {
                // Implementation of the 'init' vfunc that
                // Gio.Initable defines.
                init: function(cancellable) {
                    this.inited = true;
                }
            }
        });

 Makefile.am                  |    1 +
 gi/function.c                |   14 +---
 gi/object.c                  |  100 ++------------------
 gi/vfuncwrapper.c            |  206 ++++++++++++++++++++++++++++++++++++++++++
 gjs/jsapi-util.h             |   19 +++-
 modules/lang.js              |    7 ++-
 modules/overrides/GObject.js |   19 ++++-
 test/js/testGIMarshalling.js |   38 ++++----
 test/js/testGObjectClass.js  |    6 +-
 9 files changed, 278 insertions(+), 132 deletions(-)
---
diff --git a/Makefile.am b/Makefile.am
index 83c6ebb..0d5c4dd 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -136,6 +136,7 @@ libgjs_la_SOURCES += \
         gi/value.c	\
 	gi/interface.c	\
 	gi/gtype.c
+EXTRA_DIST += gi/vfuncwrapper.c
 
 if ENABLE_DTRACE
 gjs_gi_probes.h: gi/gjs_gi_probes.d
diff --git a/gi/function.c b/gi/function.c
index 48f4569..bc00caf 100644
--- a/gi/function.c
+++ b/gi/function.c
@@ -1554,7 +1554,6 @@ gjs_define_function(JSContext      *context,
     JSObject *function = NULL;
     GIInfoType info_type;
     gchar *name;
-    gboolean free_name;
 
     info_type = g_base_info_get_type((GIBaseInfo *)info);
 
@@ -1568,15 +1567,7 @@ gjs_define_function(JSContext      *context,
         return NULL;
     }
 
-    if (info_type == GI_INFO_TYPE_FUNCTION) {
-        name = (gchar *) g_base_info_get_name((GIBaseInfo*) info);
-        free_name = FALSE;
-    } else if (info_type == GI_INFO_TYPE_VFUNC) {
-        name = g_strdup_printf("do_%s", g_base_info_get_name((GIBaseInfo*) info));
-        free_name = TRUE;
-    } else {
-        g_assert_not_reached ();
-    }
+    name = (gchar *) g_base_info_get_name((GIBaseInfo*) info);
 
     if (!JS_DefineProperty(context, in_object, name,
                            OBJECT_TO_JSVAL(function),
@@ -1588,9 +1579,6 @@ gjs_define_function(JSContext      *context,
         return NULL;
     }
 
-    if (free_name)
-        g_free(name);
-
     JS_EndRequest(context);
     return function;
 }
diff --git a/gi/object.c b/gi/object.c
index 7535a84..b59988b 100644
--- a/gi/object.c
+++ b/gi/object.c
@@ -296,60 +296,7 @@ object_instance_set_prop(JSContext *context,
     return ret;
 }
 
-static gboolean
-is_vfunc_unchanged(GIVFuncInfo *info,
-                   GType        gtype)
-{
-    GType ptype = g_type_parent(gtype);
-    GError *error = NULL;
-    gpointer addr1, addr2;
-
-    addr1 = g_vfunc_info_get_address(info, gtype, &error);
-    if (error) {
-        g_clear_error(&error);
-        return FALSE;
-    }
-
-    addr2 = g_vfunc_info_get_address(info, ptype, &error);
-    if (error) {
-        g_clear_error(&error);
-        return FALSE;
-    }
-
-    return addr1 == addr2;
-}
-
-static GIVFuncInfo *
-find_vfunc_on_parent(GIObjectInfo *info,
-                     gchar        *name)
-{
-    GIVFuncInfo *vfunc = NULL;
-    GIObjectInfo *parent, *old_parent;
-
-    /* ref the first info so that we don't destroy
-     * it when unrefing parents later */
-    g_base_info_ref(info);
-    parent = info;
-
-    /* Since it isn't possible to override a vfunc on
-     * an interface without reimplementing it, we don't need
-     * to search the parent types when looking for a vfunc. */
-    vfunc = g_object_info_find_vfunc_using_interfaces(parent, name, NULL);
-    while (!vfunc && parent) {
-        old_parent = parent;
-        parent = g_object_info_get_parent(old_parent);
-        if (!parent)
-            break;
-
-        g_base_info_unref(old_parent);
-        vfunc = g_object_info_find_vfunc(parent, name);
-    }
-
-    if (parent)
-        g_base_info_unref(parent);
-
-    return vfunc;
-}
+#include "vfuncwrapper.c"
 
 /*
  * Like JSResolveOp, but flags provide contextual information as follows:
@@ -459,43 +406,16 @@ object_instance_new_resolve(JSContext *context,
             goto out;
         }
 
-        if (g_str_has_prefix (name, "do_")) {
-            /* The only time we find a vfunc info is when we're the base
-             * class that defined the vfunc. If we let regular prototype
-             * chaining resolve this, we'd have the implementation for the base's
-             * vfunc on the base class, without any other "real" implementations
-             * in the way. If we want to expose a "real" vfunc implementation,
-             * we need to go down to the parent infos and look at their VFuncInfos.
-             *
-             * This is good, but it's memory-hungry -- we would define every
-             * possible vfunc on every possible object, even if it's the same
-             * "real" vfunc underneath. Instead, only expose vfuncs that are
-             * different from their parent, and let prototype chaining do the
-             * rest.
-             */
-
-            gchar *name_without_do = &name[3];
-            GIVFuncInfo *vfunc;
-
-            vfunc = find_vfunc_on_parent(priv->info, name_without_do);
-            if (vfunc == NULL) {
-                ret = JS_TRUE;
-                goto out;
+        if (strcmp(name, "$gobject_vfuncs") == 0) {
+            JSObject *vfunc_wrapper;
+            vfunc_wrapper = gjs_vfunc_wrapper_new(context, obj);
+            if (vfunc_wrapper != NULL) {
+                JS_DefineProperty(context, obj, "$gobject_vfuncs",
+                                  OBJECT_TO_JSVAL(vfunc_wrapper),
+                                  NULL, NULL,
+                                  GJS_MODULE_PROP_FLAGS);
+                *objp = obj;
             }
-
-            /* In the event that the vfunc is unchanged, let regular
-             * prototypal inheritance take over. */
-            if (is_vfunc_unchanged(vfunc, priv->gtype)) {
-                g_base_info_unref((GIBaseInfo *)vfunc);
-                ret = JS_TRUE;
-                goto out;
-            }
-
-            gjs_define_function(context, obj, priv->gtype, vfunc);
-            *objp = obj;
-            g_base_info_unref((GIBaseInfo *)vfunc);
-            ret = JS_TRUE;
-            goto out;
         }
 
         /* find_method does not look at methods on parent classes,
diff --git a/gi/vfuncwrapper.c b/gi/vfuncwrapper.c
new file mode 100644
index 0000000..44a52d7
--- /dev/null
+++ b/gi/vfuncwrapper.c
@@ -0,0 +1,206 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (c) 2008  litl, LLC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+/* This file is part of object.c - it has been split up to make
+ * maintainership easier. */
+
+static jsval gjs_vfunc_wrapper_create_proto (JSContext       *context,
+                                             JSObject        *module,
+                                             const char      *proto_name,
+                                             JSObject        *parent);
+
+static gboolean
+is_vfunc_unchanged(GIVFuncInfo *info,
+                   GType        gtype)
+{
+    GType ptype = g_type_parent(gtype);
+    GError *error = NULL;
+    gpointer addr1, addr2;
+
+    addr1 = g_vfunc_info_get_address(info, gtype, &error);
+    if (error) {
+        g_clear_error(&error);
+        return FALSE;
+    }
+
+    addr2 = g_vfunc_info_get_address(info, ptype, &error);
+    if (error) {
+        g_clear_error(&error);
+        return FALSE;
+    }
+
+    return addr1 == addr2;
+}
+
+static GIVFuncInfo *
+find_vfunc_on_parent(GIObjectInfo *info,
+                     gchar        *name)
+{
+    GIVFuncInfo *vfunc = NULL;
+    GIObjectInfo *parent, *old_parent;
+
+    /* ref the first info so that we don't destroy
+     * it when unrefing parents later */
+    g_base_info_ref(info);
+    parent = info;
+
+    /* Since it isn't possible to override a vfunc on
+     * an interface without reimplementing it, we don't need
+     * to search the parent types when looking for a vfunc. */
+    vfunc = g_object_info_find_vfunc_using_interfaces(parent, name, NULL);
+    while (!vfunc && parent) {
+        old_parent = parent;
+        parent = g_object_info_get_parent(old_parent);
+        if (!parent)
+            break;
+
+        g_base_info_unref(old_parent);
+        vfunc = g_object_info_find_vfunc(parent, name);
+    }
+
+    if (parent)
+        g_base_info_unref(parent);
+
+    return vfunc;
+}
+
+static JSBool
+gjs_vfunc_wrapper_new_resolve(JSContext *context,
+                              JSObject  *obj,
+                              jsid       id,
+                              uintN      flags,
+                              JSObject **objp)
+{
+    ObjectInstance *priv;
+    char *name;
+    JSObject *owner;
+    GIVFuncInfo *vfunc;
+
+    if (!gjs_get_string_id(context, id, &name))
+        return JS_TRUE;
+
+    owner = JS_GetPrivate(context, obj);
+    if (owner == NULL)
+        goto out;
+
+    priv = priv_from_js(context, owner);
+    if (priv == NULL)
+        goto out;
+
+    /* The only time we find a vfunc info is when we're the base
+     * class that defined the vfunc. If we let regular prototype
+     * chaining resolve this, we'd have the implementation for the base's
+     * vfunc on the base class, without any other "real" implementations
+     * in the way. If we want to expose a "real" vfunc implementation,
+     * we need to go down to the parent infos and look at their VFuncInfos.
+     *
+     * This is good, but it's memory-hungry -- we would define every
+     * possible vfunc on every possible object, even if it's the same
+     * "real" vfunc underneath. Instead, only expose vfuncs that are
+     * different from their parent, and let prototype chaining do the
+     * rest.
+     */
+
+    vfunc = find_vfunc_on_parent(priv->info, name);
+    if (vfunc == NULL)
+        goto out;
+
+    /* In the event that the vfunc is unchanged, let regular
+     * prototypal inheritance take over. */
+    if (is_vfunc_unchanged(vfunc, priv->gtype)) {
+        g_base_info_unref((GIBaseInfo *)vfunc);
+        goto out;
+    }
+
+    gjs_define_function(context, obj, priv->gtype, vfunc);
+    *objp = obj;
+    g_base_info_unref((GIBaseInfo *)vfunc);
+
+ out:
+    g_free(name);
+    return JS_TRUE;
+}
+
+
+/* Default spidermonkey toString is worthless.  Replace it
+ * with something that gives us both the introspection name
+ * and a memory address.
+ */
+static JSBool
+vfunc_wrapper_to_string_func(JSContext *context,
+                             uintN      argc,
+                             jsval     *vp)
+{
+    JSBool ret = JS_FALSE;
+    JSObject *obj = JS_THIS_OBJECT(context, vp);
+    JSObject *owner = JS_GetPrivate(context, obj);
+    gchar *owner_str;
+    gchar *strval;
+    jsval retval;
+
+    owner_str = gjs_value_debug_string(context, OBJECT_TO_JSVAL(owner));
+    strval = g_strdup_printf("[object VFuncWrapper for %s]", owner_str);
+    g_free(owner_str);
+
+    ret = gjs_string_from_utf8(context, strval, -1, &retval);
+    if (ret)
+        JS_SET_RVAL(context, vp, retval);
+    g_free(strval);
+    return ret;
+}
+
+static JSPropertySpec gjs_vfunc_wrapper_proto_props[] = {
+    { NULL }
+};
+
+static JSFunctionSpec gjs_vfunc_wrapper_proto_funcs[] = {
+    { "toString", (JSNative)vfunc_wrapper_to_string_func, 0, 0 },
+    { NULL }
+};
+
+_GJS_DUMMY_FINALIZE(vfunc_wrapper)
+_GJS_DEFINE_PROTO_FULL("VFuncWrapper", vfunc_wrapper, NULL);
+
+static JSObject *
+gjs_vfunc_wrapper_new (JSContext *context,
+                       JSObject  *owner)
+{
+    JSObject *object;
+    JSObject *global;
+
+    JS_BeginRequest(context);
+
+    /* put constructor for VFuncWrapper() in the global namespace */
+    global = gjs_get_import_global(context);
+    gjs_vfunc_wrapper_create_proto(context, global, "VFuncWrapper", NULL);
+
+    object = JS_NewObject(context, &gjs_vfunc_wrapper_class, NULL, NULL);
+    if (object == NULL)
+        goto out;
+
+    JS_SetPrivate(context, object, owner);
+
+ out:
+    JS_EndRequest(context);
+    return object;
+}
diff --git a/gjs/jsapi-util.h b/gjs/jsapi-util.h
index 0e186f9..a2db4aa 100644
--- a/gjs/jsapi-util.h
+++ b/gjs/jsapi-util.h
@@ -114,6 +114,7 @@ typedef struct GjsRootedArray GjsRootedArray;
  */
 #define GJS_DEFINE_PROTO(tn, cn) \
 GJS_NATIVE_CONSTRUCTOR_DECLARE(cn); \
+_GJS_DUMMY_RESOLVE(cn) \
 _GJS_DEFINE_PROTO_FULL(tn, cn, gjs_##cn##_constructor)
 
 /**
@@ -126,12 +127,15 @@ _GJS_DEFINE_PROTO_FULL(tn, cn, gjs_##cn##_constructor)
  * you won't be able to instantiate it using the new keyword
  */
 #define GJS_DEFINE_PROTO_ABSTRACT(tn, cn) \
+_GJS_DUMMY_RESOLVE(cn) \
 _GJS_DEFINE_PROTO_FULL(tn, cn, NULL)
 
-#define _GJS_DEFINE_PROTO_FULL(type_name, cname, ctor) \
-static JSPropertySpec gjs_##cname##_proto_props[]; \
-static JSFunctionSpec gjs_##cname##_proto_funcs[]; \
-static void gjs_##cname##_finalize(JSContext *context, JSObject *obj); \
+#define _GJS_DUMMY_FINALIZE(cname) \
+static void gjs_##cname##_finalize(JSContext *context, JSObject *obj) \
+{ \
+}
+
+#define _GJS_DUMMY_RESOLVE(cname) \
 static JSBool gjs_##cname##_new_resolve(JSContext *context, \
                                         JSObject  *obj, \
                                         jsval      id, \
@@ -139,7 +143,12 @@ static JSBool gjs_##cname##_new_resolve(JSContext *context, \
                                         JSObject **objp) \
 { \
     return JS_TRUE; \
-} \
+}
+
+#define _GJS_DEFINE_PROTO_FULL(type_name, cname, ctor) \
+static JSPropertySpec gjs_##cname##_proto_props[]; \
+static JSFunctionSpec gjs_##cname##_proto_funcs[]; \
+static void gjs_##cname##_finalize(JSContext *context, JSObject *obj); \
 static struct JSClass gjs_##cname##_class = { \
     type_name, \
     JSCLASS_HAS_PRIVATE | \
diff --git a/modules/lang.js b/modules/lang.js
index 9e5c6c4..0956740 100644
--- a/modules/lang.js
+++ b/modules/lang.js
@@ -158,7 +158,12 @@ function _parent() {
     let name = caller._name;
     let parent = caller._owner.__super__;
 
-    let previous = parent ? parent.prototype[name] : undefined;
+    let lookupOnParent = caller._lookupOnParent;
+    let previous;
+    if (lookupOnParent)
+        previous = parent ? lookupOnParent(parent, name) : undefined;
+    else
+        previous = parent ? parent.prototype[name] : undefined;
 
     if (!previous)
         throw new TypeError("The method '" + name + "' is not on the superclass");
diff --git a/modules/overrides/GObject.js b/modules/overrides/GObject.js
index bedd4bb..153d526 100644
--- a/modules/overrides/GObject.js
+++ b/modules/overrides/GObject.js
@@ -24,6 +24,10 @@ const Gi = imports._gi;
 
 let GObject;
 
+function lookupOnParentVFunc(parent, name) {
+    return parent.prototype.$gobject_vfuncs[name];
+}
+
 const GObjectMeta = new Lang.Class({
     Name: 'GObjectClass',
     Extends: Lang.Class,
@@ -36,10 +40,12 @@ const GObjectMeta = new Lang.Class({
         let properties = params.Properties;
         let signals = params.Signals;
         let ifaces = params.Implements;
+        let vfuncImpls = params.VFuncImpls;
 
         delete params.Properties;
         delete params.Signals;
         delete params.Implements;
+        delete params.VFuncImpls;
 
         if (properties) {
             for (let prop in properties) {
@@ -68,12 +74,19 @@ const GObjectMeta = new Lang.Class({
                 Gi.add_interface(this.prototype, ifaces[i]);
         }
 
+        if (vfuncImpls) {
+            for (let prop in vfuncImpls) {
+                let value = this.wrapFunction(prop, vfuncImpls[prop]);
+                value._lookupOnParent = lookupOnParentVFunc;
+                this.prototype.$gobject_vfuncs[prop] = value;
+                Gi.hook_up_vfunc(this.prototype, prop, value);
+            }
+        }
+
         for (let prop in params) {
             let value = this.prototype[prop];
             if (typeof value === 'function') {
-                if (prop.slice(0, 3) == 'do_') {
-                    Gi.hook_up_vfunc(this.prototype, prop.slice(3), value);
-                } else if (prop.slice(0, 3) == 'on_') {
+                if (prop.slice(0, 3) == 'on_') {
                     let id = GObject.signal_lookup(prop.slice(3).replace('_', '-'), this.$gtype);
                     if (id != 0) {
                         GObject.signal_override_class_closure(id, this.$gtype, function() {
diff --git a/test/js/testGIMarshalling.js b/test/js/testGIMarshalling.js
index 252c96b..c7cf3ed 100644
--- a/test/js/testGIMarshalling.js
+++ b/test/js/testGIMarshalling.js
@@ -234,24 +234,26 @@ const VFuncTester = new Lang.Class({
     Name: 'VFuncTester',
     Extends: GIMarshallingTests.Object,
 
-    do_vfunc_return_value_only: function(obj) {
-        return 42;
-    },
-
-    do_vfunc_one_out_parameter: function(obj) {
-        return 43;
-    },
-
-    do_vfunc_multiple_out_parameters: function(obj) {
-        return [44, 45];
-    },
-
-    do_vfunc_return_value_and_one_out_parameter: function(obj) {
-        return [46, 47];
-    },
-
-    do_vfunc_return_value_and_multiple_out_parameters: function(obj) {
-        return [48, 49, 50];
+    VFuncImpls: {
+        vfunc_return_value_only: function(obj) {
+            return 42;
+        },
+
+        vfunc_one_out_parameter: function(obj) {
+            return 43;
+        },
+
+        vfunc_multiple_out_parameters: function(obj) {
+            return [44, 45];
+        },
+
+        vfunc_return_value_and_one_out_parameter: function(obj) {
+            return [46, 47];
+        },
+
+        vfunc_return_value_and_multiple_out_parameters: function(obj) {
+            return [48, 49, 50];
+        }
     }
 });
 
diff --git a/test/js/testGObjectClass.js b/test/js/testGObjectClass.js
index 20a1e49..2b3efa3 100644
--- a/test/js/testGObjectClass.js
+++ b/test/js/testGObjectClass.js
@@ -141,8 +141,10 @@ const MyInitable = new Lang.Class({
         this.inited = false;
     },
 
-    do_init: function(cancellable) { // error?
-        this.inited = true;
+    VFuncImpls: {
+        init: function(cancellable) { // error?
+            this.inited = true;
+        }
     }
 });
 



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