[gjs/wip/gobj-kitchen-sink: 4/21] Expose parent vfuncs on the parent prototype, prefixed with "do_"



commit e21cb74e6dd321f0f78ce557b565817eb90852d1
Author: Jasper St. Pierre <jstpierre mecheye net>
Date:   Mon Nov 28 13:55:52 2011 -0500

    Expose parent vfuncs on the parent prototype, prefixed with "do_"
    
    As an example, the implementation of ClutterActor::get_preferred_width
    will be exposed as Clutter.Actor.prototype.do_get_preferred_width.
    We need this so that GObject subclasses can chain up when implementing
    their own vfunc overrides.
    
    The main big change here is that we now require the GType when using
    gjs_define_function. This is because the implementation's GType may
    not be the GICallableInfo's owner: ClutterRectangle has its own
    get_preferred_width implementation, but since the vfunc isn't declared
    in ClutterRectangleClass, introspection for ClutterRectangle doesn't
    have a GIVFuncInfo. If a user created a ClutterRectangle subclass, in
    order to chain up properly we need to expose
    Clutter.Rectangle.prototype.do_get_preferred_width.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=663492

 gi/boxed.c     |   12 ++++-
 gi/function.c  |  123 +++++++++++++++++++++++++++++++++++++++++++++----------
 gi/function.h  |    8 +++-
 gi/interface.c |    9 ++--
 gi/object.c    |   99 +++++++++++++++++++++++++++++++++++++++++++-
 gi/repo.c      |    2 +-
 gi/union.c     |    4 +-
 7 files changed, 220 insertions(+), 37 deletions(-)
---
diff --git a/gi/boxed.c b/gi/boxed.c
index ef80395..99b778d 100644
--- a/gi/boxed.c
+++ b/gi/boxed.c
@@ -62,6 +62,7 @@ GJS_DEFINE_DYNAMIC_PRIV_FROM_JS(Boxed, gjs_boxed_class)
 static JSBool
 gjs_define_static_methods(JSContext    *context,
                           JSObject     *constructor,
+                          GType         gtype,
                           GIStructInfo *boxed_info)
 {
     int i;
@@ -84,7 +85,8 @@ gjs_define_static_methods(JSContext    *context,
          * like in the near future.
          */
         if (!(flags & GI_FUNCTION_IS_METHOD)) {
-            gjs_define_function(context, constructor, meth_info);
+            gjs_define_function(context, constructor, gtype,
+                                (GICallableInfo *)meth_info);
         }
 
         g_base_info_unref((GIBaseInfo*) meth_info);
@@ -153,7 +155,9 @@ boxed_new_resolve(JSContext *context,
 
             boxed_proto = obj;
 
-            if (gjs_define_function(context, boxed_proto, method_info) == NULL) {
+            if (gjs_define_function(context, boxed_proto,
+                                    g_registered_type_info_get_g_type (priv->info),
+                                    (GICallableInfo *)method_info) == NULL) {
                 g_base_info_unref( (GIBaseInfo*) method_info);
                 goto out;
             }
@@ -1175,7 +1179,9 @@ gjs_define_boxed_class(JSContext    *context,
     }
 
     constructor = JSVAL_TO_OBJECT(value);
-    gjs_define_static_methods (context, constructor, info);
+    gjs_define_static_methods (context, constructor,
+                               g_registered_type_info_get_g_type (priv->info),
+                               priv->info);
 
     if (constructor_p)
         *constructor_p = constructor;
diff --git a/gi/function.c b/gi/function.c
index 4ce4780..549ae40 100644
--- a/gi/function.c
+++ b/gi/function.c
@@ -129,7 +129,8 @@ gjs_callback_trampoline_unref(GjsCallbackTrampoline *trampoline)
 
         context = gjs_runtime_get_current_context(trampoline->runtime);
 
-        JS_RemoveValueRoot(context, &trampoline->js_function);
+        if (!trampoline->is_vfunc)
+            JS_RemoveValueRoot(context, &trampoline->js_function);
         g_callable_info_free_closure(trampoline->info, trampoline->closure);
         g_base_info_unref( (GIBaseInfo*) trampoline->info);
         g_slice_free(GjsCallbackTrampoline, trampoline);
@@ -210,6 +211,7 @@ gjs_callback_closure(ffi_cif *cif,
     GjsCallbackTrampoline *trampoline;
     int i, n_args, n_jsargs, n_outargs;
     jsval *jsargs, rval;
+    JSObject *this_object;
     GITypeInfo ret_type;
     gboolean success = FALSE;
     gboolean ret_type_is_void;
@@ -252,8 +254,13 @@ gjs_callback_closure(ffi_cif *cif,
             goto out;
     }
 
+    if (trampoline->is_vfunc)
+        this_object = JSVAL_TO_OBJECT(jsargs[0]);
+    else
+        this_object = NULL;
+
     if (!JS_CallFunctionValue(context,
-                              NULL,
+                              this_object,
                               trampoline->js_function,
                               n_jsargs,
                               jsargs,
@@ -393,7 +400,8 @@ GjsCallbackTrampoline*
 gjs_callback_trampoline_new(JSContext      *context,
                             jsval           function,
                             GICallableInfo *callable_info,
-                            GIScopeType     scope)
+                            GIScopeType     scope,
+                            gboolean        is_vfunc)
 {
     GjsCallbackTrampoline *trampoline;
 
@@ -409,11 +417,14 @@ gjs_callback_trampoline_new(JSContext      *context,
     trampoline->info = callable_info;
     g_base_info_ref((GIBaseInfo*)trampoline->info);
     trampoline->js_function = function;
-    JS_AddValueRoot(context, &trampoline->js_function);
+
+    if (!is_vfunc)
+        JS_AddValueRoot(context, &trampoline->js_function);
     trampoline->closure = g_callable_info_prepare_closure(callable_info, &trampoline->cif,
                                                           gjs_callback_closure, trampoline);
 
     trampoline->scope = scope;
+    trampoline->is_vfunc = is_vfunc;
 
     return trampoline;
 }
@@ -561,10 +572,10 @@ gjs_invoke_c_function(JSContext      *context,
     GError *local_error = NULL;
     gboolean failed, postinvoke_release_failed;
 
-    GIFunctionInfoFlags flags;
     gboolean is_method;
     GITypeInfo return_info;
     GITypeTag return_tag;
+    GIInfoType info_type;
     jsval *return_values = NULL;
     guint8 next_rval = 0; /* index into return_values */
     GSList *iter;
@@ -582,9 +593,26 @@ gjs_invoke_c_function(JSContext      *context,
         completed_trampolines = NULL;
     }
 
-    flags = g_function_info_get_flags(function->info);
-    is_method = (flags & GI_FUNCTION_IS_METHOD) != 0;
-    can_throw_gerror = (flags & GI_FUNCTION_THROWS) != 0;
+    info_type = g_base_info_get_type((GIBaseInfo *)function->info);
+
+    switch (info_type) {
+    case GI_INFO_TYPE_FUNCTION:
+        {
+            GIFunctionInfoFlags flags;
+            flags = g_function_info_get_flags((GIFunctionInfo *)function->info);
+            is_method = (flags & GI_FUNCTION_IS_METHOD) != 0;
+            can_throw_gerror = (flags & GI_FUNCTION_THROWS) != 0;
+        }
+        break;
+    case GI_INFO_TYPE_CALLBACK:
+    case GI_INFO_TYPE_VFUNC:
+        is_method = TRUE;
+        can_throw_gerror = FALSE;
+        break;
+    default:
+        g_assert_not_reached();
+    }
+
     c_argc = function->invoker.cif.nargs;
     gi_argc = g_callable_info_get_n_args( (GICallableInfo*) function->info);
 
@@ -749,7 +777,8 @@ gjs_invoke_c_function(JSContext      *context,
                     trampoline = gjs_callback_trampoline_new(context,
                                                              value,
                                                              callable_info,
-                                                             scope);
+                                                             scope,
+                                                             FALSE);
                     closure = trampoline->closure;
                     g_base_info_unref(callable_info);
                 }
@@ -1271,10 +1300,14 @@ function_to_string (JSContext *context,
     if (priv == NULL) {
         string = "function () {\n}";
         free = FALSE;
-    } else {
+    } else if (g_base_info_get_type(priv->info) == GI_INFO_TYPE_FUNCTION) {
         string = g_strdup_printf("function %s(){\n\t/* proxy for native symbol %s(); */\n}",
                                  g_base_info_get_name ((GIBaseInfo *) priv->info),
-                                 g_function_info_get_symbol (priv->info));
+                                 g_function_info_get_symbol ((GIFunctionInfo *) priv->info));
+        free = TRUE;
+    } else {
+        string = g_strdup_printf("function %s(){\n\t/* proxy for native symbol */\n}",
+                                 g_base_info_get_name ((GIBaseInfo *) priv->info));
         free = TRUE;
     }
 
@@ -1330,15 +1363,40 @@ static JSFunctionSpec gjs_function_proto_funcs[] = {
 static gboolean
 init_cached_function_data (JSContext      *context,
                            Function       *function,
-                           GIFunctionInfo *info)
+                           GType           gtype,
+                           GICallableInfo *info)
 {
     guint8 i, n_args, array_length_pos;
     GError *error = NULL;
     GITypeInfo return_type;
+    GIInfoType info_type;
 
-    if (!g_function_info_prep_invoker(info, &(function->invoker), &error)) {
-        gjs_throw_g_error(context, error);
-        return FALSE;
+    info_type = g_base_info_get_type((GIBaseInfo *)info);
+
+    if (info_type == GI_INFO_TYPE_FUNCTION) {
+        if (!g_function_info_prep_invoker((GIFunctionInfo *)info,
+                                          &(function->invoker),
+                                          &error)) {
+            gjs_throw_g_error(context, error);
+            return FALSE;
+        }
+    } else if (info_type == GI_INFO_TYPE_VFUNC) {
+        gpointer addr;
+
+        addr = g_vfunc_info_get_address((GIVFuncInfo *)info, gtype, &error);
+        if (error != NULL) {
+            if (error->code != G_INVOKE_ERROR_SYMBOL_NOT_FOUND)
+                gjs_throw_g_error(context, error);
+
+            return FALSE;
+        }
+
+        if (!g_function_invoker_new_for_address(addr, info,
+                                                &(function->invoker),
+                                                &error)) {
+            gjs_throw_g_error(context, error);
+            return FALSE;
+        }
     }
 
     g_callable_info_load_return_type((GICallableInfo*)info, &return_type);
@@ -1448,7 +1506,8 @@ init_cached_function_data (JSContext      *context,
 
 static JSObject*
 function_new(JSContext      *context,
-             GIFunctionInfo *info)
+             GType           gtype,
+             GICallableInfo *info)
 {
     JSObject *function;
     JSObject *global;
@@ -1504,7 +1563,7 @@ function_new(JSContext      *context,
     }
 
     priv = priv_from_js(context, function);
-    if (!init_cached_function_data(context, priv, info))
+    if (!init_cached_function_data(context, priv, gtype, (GICallableInfo *)info))
       return NULL;
 
     return function;
@@ -1513,13 +1572,19 @@ function_new(JSContext      *context,
 JSObject*
 gjs_define_function(JSContext      *context,
                     JSObject       *in_object,
-                    GIFunctionInfo *info)
+                    GType           gtype,
+                    GICallableInfo *info)
 {
-    JSObject *function;
+    JSObject *function = NULL;
+    GIInfoType info_type;
+    gchar *name;
+    gboolean free_name;
+
+    info_type = g_base_info_get_type((GIBaseInfo *)info);
 
     JS_BeginRequest(context);
 
-    function = function_new(context, info);
+    function = function_new(context, gtype, info);
     if (function == NULL) {
         gjs_move_exception(context, context);
 
@@ -1527,8 +1592,17 @@ gjs_define_function(JSContext      *context,
         return NULL;
     }
 
-    if (!JS_DefineProperty(context, in_object,
-                           g_base_info_get_name( (GIBaseInfo*) info),
+    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 ();
+    }
+
+    if (!JS_DefineProperty(context, in_object, name,
                            OBJECT_TO_JSVAL(function),
                            NULL, NULL,
                            GJS_MODULE_PROP_FLAGS)) {
@@ -1538,6 +1612,9 @@ gjs_define_function(JSContext      *context,
         return NULL;
     }
 
+    if (free_name)
+        g_free(name);
+
     JS_EndRequest(context);
     return function;
 }
@@ -1555,7 +1632,7 @@ gjs_invoke_c_function_uncached (JSContext      *context,
   JSBool result;
 
   memset (&function, 0, sizeof (Function));
-  if (!init_cached_function_data (context, &function, info))
+  if (!init_cached_function_data (context, &function, 0, info))
     return JS_FALSE;
 
   result = gjs_invoke_c_function (context, &function, obj, argc, argv, rval);
diff --git a/gi/function.h b/gi/function.h
index 555def6..875144e 100644
--- a/gi/function.h
+++ b/gi/function.h
@@ -41,19 +41,23 @@ typedef struct {
     ffi_cif cif;
     ffi_closure *closure;
     GIScopeType scope;
+    gboolean is_vfunc;
 } GjsCallbackTrampoline;
 
 GjsCallbackTrampoline* gjs_callback_trampoline_new(JSContext      *context,
                                                    jsval           function,
                                                    GICallableInfo *callable_info,
-                                                   GIScopeType     scope);
+                                                   GIScopeType     scope,
+                                                   gboolean        is_vfunc);
 
 void gjs_callback_trampoline_unref(GjsCallbackTrampoline *trampoline);
 void gjs_callback_trampoline_ref(GjsCallbackTrampoline *trampoline);
 
 JSObject* gjs_define_function   (JSContext      *context,
                                  JSObject       *in_object,
-                                 GIFunctionInfo *info);
+                                 GType           gtype,
+                                 GICallableInfo *info);
+
 JSBool    gjs_invoke_c_function_uncached (JSContext      *context,
                                           GIFunctionInfo *info,
                                           JSObject       *obj,
diff --git a/gi/interface.c b/gi/interface.c
index 87ad7b2..245e037 100644
--- a/gi/interface.c
+++ b/gi/interface.c
@@ -36,6 +36,7 @@
 
 typedef struct {
     GIInterfaceInfo *info;
+    GType gtype;
 } Interface;
 
 static struct JSClass gjs_interface_class;
@@ -105,7 +106,7 @@ gjs_define_static_methods(JSContext       *context,
          * like in the near future.
          */
         if (!(flags & GI_FUNCTION_IS_METHOD)) {
-            gjs_define_function(context, constructor,
+            gjs_define_function(context, constructor, gtype,
                                 (GICallableInfo *)meth_info);
         }
 
@@ -140,6 +141,7 @@ interface_new_resolve(JSContext *context,
 
     if (method_info != NULL) {
         if (gjs_define_function(context, obj,
+                                priv->gtype,
                                 (GICallableInfo*)method_info) == NULL) {
             g_base_info_unref((GIBaseInfo*)method_info);
             goto out;
@@ -254,14 +256,13 @@ gjs_define_interface_class(JSContext       *context,
 
     priv = g_slice_new0(Interface);
     priv->info = info;
+    priv->gtype = g_registered_type_info_get_g_type(priv->info);
     g_base_info_ref((GIBaseInfo*)priv->info);
     JS_SetPrivate(context, prototype, priv);
 
     gjs_object_get_property(context, in_object, constructor_name, &value);
     constructor = JSVAL_TO_OBJECT(value);
-    gjs_define_static_methods(context, constructor,
-                              g_registered_type_info_get_g_type(priv->info),
-                              priv->info);
+    gjs_define_static_methods(context, constructor, priv->gtype, priv->info);
 
     if (prototype_p)
         *prototype_p = prototype;
diff --git a/gi/object.c b/gi/object.c
index effc6d5..4b91931 100644
--- a/gi/object.c
+++ b/gi/object.c
@@ -233,6 +233,57 @@ 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;
+    vfunc = g_object_info_find_vfunc(parent, name);
+    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;
+}
+
 /*
  * Like JSResolveOp, but flags provide contextual information as follows:
  *
@@ -281,6 +332,45 @@ object_instance_new_resolve(JSContext *context,
         /* We are the prototype, so look for methods and other class properties */
         GIFunctionInfo *method_info;
 
+        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;
+            }
+
+            /* 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,
          * we rely on javascript to walk up the __proto__ chain
          * and find those and define them in the right prototype.
@@ -353,7 +443,8 @@ object_instance_new_resolve(JSContext *context,
                       g_base_info_get_namespace( (GIBaseInfo*) priv->info),
                       g_base_info_get_name( (GIBaseInfo*) priv->info));
 
-            if (gjs_define_function(context, obj, method_info) == NULL) {
+            if (gjs_define_function(context, obj, priv->gtype,
+                                    (GICallableInfo *)method_info) == NULL) {
                 g_base_info_unref( (GIBaseInfo*) method_info);
                 goto out;
             }
@@ -1153,6 +1244,7 @@ static JSFunctionSpec gjs_object_instance_proto_funcs[] = {
 static JSBool
 gjs_define_static_methods(JSContext    *context,
                           JSObject     *constructor,
+                          GType         gtype,
                           GIObjectInfo *object_info)
 {
     int i;
@@ -1175,7 +1267,8 @@ gjs_define_static_methods(JSContext    *context,
          * like in the near future.
          */
         if (!(flags & GI_FUNCTION_IS_METHOD)) {
-            gjs_define_function(context, constructor, meth_info);
+            gjs_define_function(context, constructor, gtype,
+                                (GICallableInfo *)meth_info);
         }
 
         g_base_info_unref((GIBaseInfo*) meth_info);
@@ -1393,7 +1486,7 @@ gjs_define_object_class(JSContext     *context,
        }
 
        constructor = JSVAL_TO_OBJECT(value);
-       gjs_define_static_methods(context, constructor, info);
+       gjs_define_static_methods(context, constructor, gtype, info);
     }
 
     value = OBJECT_TO_JSVAL(gjs_gtype_create_gtype_wrapper(context, gtype));
diff --git a/gi/repo.c b/gi/repo.c
index 08a6d4a..fa6a585 100644
--- a/gi/repo.c
+++ b/gi/repo.c
@@ -460,7 +460,7 @@ gjs_define_info(JSContext  *context,
     case GI_INFO_TYPE_FUNCTION:
         {
             JSObject *f;
-            f = gjs_define_function(context, in_object, (GIFunctionInfo*) info);
+            f = gjs_define_function(context, in_object, 0, (GICallableInfo*) info);
             if (f == NULL)
                 return JS_FALSE;
         }
diff --git a/gi/union.c b/gi/union.c
index ac921b2..ec02af0 100644
--- a/gi/union.c
+++ b/gi/union.c
@@ -112,7 +112,9 @@ union_new_resolve(JSContext *context,
 
             union_proto = obj;
 
-            if (gjs_define_function(context, union_proto, method_info) == NULL) {
+            if (gjs_define_function(context, union_proto,
+                                    g_registered_type_info_get_g_type(priv->info),
+                                    method_info) == NULL) {
                 g_base_info_unref( (GIBaseInfo*) method_info);
                 ret = JS_FALSE;
                 goto out;



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