[gjs: 1/2] object: Resolve properties in resolve_no_info



commit 12ace06a6f1ae2a2896f0a37027734ede7c89750
Author: Philip Chimento <philip chimento gmail com>
Date:   Mon Jul 16 21:29:47 2018 -0400

    object: Resolve properties in resolve_no_info
    
    A regression from the property cache refactor caused properties like
    Gio.NetworkMonitor.network_available to disappear. This was because
    NetworkMonitor is an interface, implemented by a non-introspectable
    class, i.e. Gio.NetworkMonitor.get_default() gives you an instance of
    GNetworkMonitorBase which does not have introspection information.
    
    To fix this, we iterate through all the interfaces implemented by a
    non-introspectable type, and check if the ID to be resolved is a property
    of one of them, and define it on the prototype if so. For this we factor
    out some code into separate functions, and change resolve_no_info() to
    have a parameter specifying whether to consider only methods or both
    methods and properties.
    
    Closes: #182

 gi/object.cpp                          | 126 ++++++++++++++++++++++-----------
 gi/object.h                            |   9 ++-
 installed-tests/js/testGObjectClass.js |   7 ++
 3 files changed, 98 insertions(+), 44 deletions(-)
---
diff --git a/gi/object.cpp b/gi/object.cpp
index b7cab446..5c82fa92 100644
--- a/gi/object.cpp
+++ b/gi/object.cpp
@@ -667,11 +667,77 @@ find_vfunc_on_parents(GIObjectInfo *info,
     return vfunc;
 }
 
+/* Taken from GLib */
+static void canonicalize_key(char* key) {
+    for (char* p = key; *p != 0; p++) {
+        char c = *p;
+
+        if (c != '-' && (c < '0' || c > '9') && (c < 'A' || c > 'Z') &&
+            (c < 'a' || c > 'z'))
+            *p = '-';
+    }
+}
+
+/* @name must already be canonicalized */
+static bool is_ginterface_property_name(GIInterfaceInfo* info,
+                                        const char* name) {
+    int n_props = g_interface_info_get_n_properties(info);
+    GjsAutoInfo<GIPropertyInfo> prop_info;
+
+    for (int ix = 0; ix < n_props; ix++) {
+        prop_info = g_interface_info_get_property(info, ix);
+        if (strcmp(name, prop_info.name()) == 0)
+            break;
+        prop_info.reset();
+    }
+
+    if (!prop_info)
+        return false;
+
+    return g_property_info_get_flags(prop_info) & G_PARAM_READABLE;
+}
+
+bool ObjectPrototype::lazy_define_gobject_property(JSContext* cx,
+                                                   JS::HandleObject obj,
+                                                   JS::HandleId id,
+                                                   bool* resolved,
+                                                   const char* name) {
+    bool found = false;
+    if (!JS_AlreadyHasOwnPropertyById(cx, obj, id, &found))
+        return false;
+    if (found) {
+        /* Already defined, so *resolved = false because we didn't just
+         * define it */
+        *resolved = false;
+        return true;
+    }
+
+    debug_jsprop("Defining lazy GObject property", id, obj);
+
+    JS::RootedValue private_id(cx, JS::StringValue(JSID_TO_STRING(id)));
+    if (!gjs_define_property_dynamic(
+            cx, obj, name, "gobject_prop", &ObjectBase::prop_getter,
+            &ObjectBase::prop_setter, private_id, GJS_MODULE_PROP_FLAGS))
+        return false;
+
+    *resolved = true;
+    return true;
+}
+
 bool ObjectPrototype::resolve_no_info(JSContext* cx, JS::HandleObject obj,
-                                      bool* resolved, const char* name) {
+                                      JS::HandleId id, bool* resolved,
+                                      const char* name,
+                                      ResolveWhat resolve_props) {
     guint n_interfaces;
     guint i;
 
+    GjsAutoChar canonical_name;
+    if (resolve_props == ConsiderMethodsAndProperties) {
+        char* canonical_name_unowned = gjs_hyphen_from_camel(name);
+        canonicalize_key(canonical_name_unowned);
+        canonical_name.reset(canonical_name_unowned);
+    }
+
     GType *interfaces = g_type_interfaces(m_gtype, &n_interfaces);
     for (i = 0; i < n_interfaces; i++) {
         GjsAutoInfo<GIInterfaceInfo> iface_info =
@@ -696,6 +762,17 @@ bool ObjectPrototype::resolve_no_info(JSContext* cx, JS::HandleObject obj,
                 return true;
             }
         }
+
+        if (resolve_props == ConsiderOnlyMethods)
+            continue;
+
+        /* If the name refers to a GObject property, lazily define the property
+         * in JS as we do below in the real resolve hook. We ignore fields here
+         * because I don't think interfaces can have fields */
+        if (is_ginterface_property_name(iface_info, canonical_name)) {
+            g_free(interfaces);
+            return lazy_define_gobject_property(cx, obj, id, resolved, name);
+        }
     }
 
     *resolved = false;
@@ -703,21 +780,6 @@ bool ObjectPrototype::resolve_no_info(JSContext* cx, JS::HandleObject obj,
     return true;
 }
 
-/* Taken from GLib */
-static void
-canonicalize_key(char *key)
-{
-    for (char *p = key; *p != 0; p++) {
-        char c = *p;
-
-        if (c != '-' &&
-            (c < '0' || c > '9') &&
-            (c < 'A' || c > 'Z') &&
-            (c < 'a' || c > 'z'))
-            *p = '-';
-    }
-}
-
 static bool
 is_gobject_property_name(GIObjectInfo *info,
                          const char   *name)
@@ -784,7 +846,8 @@ bool ObjectPrototype::resolve_impl(JSContext* context, JS::HandleObject obj,
      * we need to look at exposing interfaces. Look up our interfaces through
      * GType data, and then hope that *those* are introspectable. */
     if (is_custom_js_class())
-        return resolve_no_info(context, obj, resolved, name);
+        return resolve_no_info(context, obj, id, resolved, name,
+                               ConsiderMethodsAndProperties);
 
     if (g_str_has_prefix (name, "vfunc_")) {
         /* The only time we find a vfunc info is when we're the base
@@ -824,30 +887,8 @@ bool ObjectPrototype::resolve_impl(JSContext* context, JS::HandleObject obj,
          * method resolution. */
     }
 
-    /* If the name refers to a GObject property or field, lazily define the
-     * property in JS, on the prototype. */
-    if (is_gobject_property_name(m_info, name)) {
-        bool found = false;
-        if (!JS_AlreadyHasOwnPropertyById(context, obj, id, &found))
-            return false;
-        if (found) {
-            /* Already defined, so *resolved = false because we didn't just
-             * define it */
-            *resolved = false;
-            return true;
-        }
-
-        debug_jsprop("Defining lazy GObject property", id, obj);
-
-        JS::RootedValue private_id(context, JS::StringValue(JSID_TO_STRING(id)));
-        if (!gjs_define_property_dynamic(
-                context, obj, name, "gobject_prop", &ObjectBase::prop_getter,
-                &ObjectBase::prop_setter, private_id, GJS_MODULE_PROP_FLAGS))
-            return false;
-
-        *resolved = true;
-        return true;
-    }
+    if (is_gobject_property_name(m_info, name))
+        return lazy_define_gobject_property(context, obj, id, resolved, name);
 
     GjsAutoInfo<GIFieldInfo> field_info = lookup_field_info(m_info, name);
     if (field_info) {
@@ -896,7 +937,8 @@ bool ObjectPrototype::resolve_impl(JSContext* context, JS::HandleObject obj,
      * https://bugzilla.gnome.org/show_bug.cgi?id=632922
      */
     if (!method_info)
-        return resolve_no_info(context, obj, resolved, name);
+        return resolve_no_info(context, obj, id, resolved, name,
+                               ConsiderOnlyMethods);
 
 #if GJS_VERBOSE_ENABLE_GI_USAGE
     _gjs_log_info_usage((GIBaseInfo*) method_info);
diff --git a/gi/object.h b/gi/object.h
index 4c6cd59f..7c5ea35e 100644
--- a/gi/object.h
+++ b/gi/object.h
@@ -272,8 +272,13 @@ class ObjectPrototype : public ObjectBase {
     /* Helper methods */
  private:
     bool is_vfunc_unchanged(GIVFuncInfo* info);
-    bool resolve_no_info(JSContext* cx, JS::HandleObject obj, bool* resolved,
-                         const char* name);
+    bool lazy_define_gobject_property(JSContext* cx, JS::HandleObject obj,
+                                      JS::HandleId id, bool* resolved,
+                                      const char* name);
+    enum ResolveWhat { ConsiderOnlyMethods, ConsiderMethodsAndProperties };
+    bool resolve_no_info(JSContext* cx, JS::HandleObject obj, JS::HandleId id,
+                         bool* resolved, const char* name,
+                         ResolveWhat resolve_props);
 
  public:
     void set_type_qdata(void);
diff --git a/installed-tests/js/testGObjectClass.js b/installed-tests/js/testGObjectClass.js
index fe215134..4371f598 100644
--- a/installed-tests/js/testGObjectClass.js
+++ b/installed-tests/js/testGObjectClass.js
@@ -352,4 +352,11 @@ describe('GObject class with decorator', function () {
 
         expect (() => new MyCustomCharset() && new MySecondCustomCharset()).not.toThrow();
     });
+
+    it('resolves properties from interfaces', function() {
+        const mon = Gio.NetworkMonitor.get_default();
+        expect(mon.network_available).toBeDefined();
+        expect(mon.networkAvailable).toBeDefined();
+        expect(mon['network-available']).toBeDefined();
+    });
 });


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