[gjs/ewlsh/interface-resolution: 1/2] gi: Use interface wrapper prototype to define implemented methods




commit fe5dbc8a17a0b56ee29b7adc30449cd3c2a01886
Author: Evan Welsh <contact evanwelsh com>
Date:   Wed Aug 25 18:06:16 2021 -0700

    gi: Use interface wrapper prototype to define implemented methods
    
    This change uses the GJS interface wrapper to resolve interface methods
    instead of defining unique copies for each interface method.
    This allows for overriding methods on interfaces as long as the override
    exists before the method is resolved for the first time.
    
    Fixes #189

 gi/object.cpp                              | 77 ++++++++++++++++++++++++++++--
 installed-tests/js/testGObjectInterface.js | 24 ++++++++++
 2 files changed, 97 insertions(+), 4 deletions(-)
---
diff --git a/gi/object.cpp b/gi/object.cpp
index 77acf3f4..d7313ad4 100644
--- a/gi/object.cpp
+++ b/gi/object.cpp
@@ -641,6 +641,56 @@ bool ObjectPrototype::lazy_define_gobject_property(JSContext* cx,
     return true;
 }
 
+static bool copy_interface_method_to_prototype(JSContext* cx, GIObjectInfo* iface_info,
+                                  const char* method_name,
+                                  JS::HandleObject target_prototype,
+                                  bool* found) {
+    GType gtype = g_base_info_get_type(iface_info);
+    JS::RootedObject constructor(
+        cx, gjs_lookup_object_constructor_from_info(cx, iface_info, gtype));
+    if (!constructor)
+        return false;
+
+    const GjsAtoms& atoms = GjsContextPrivate::atoms(cx);
+    JS::RootedValue v_prototype(cx);
+    if (!JS_GetPropertyById(cx, constructor, atoms.prototype(), &v_prototype))
+        return false;
+    
+    if (!v_prototype.isObject()) {
+        gjs_throw(cx, "Interface %s.%s has invalid prototype.", g_base_info_get_namespace(iface_info), 
g_base_info_get_name(iface_info));
+        return false;
+    }
+
+    JS::RootedObject prototype(cx, &v_prototype.toObject());
+    JS::RootedId method_name_id(cx, gjs_intern_string_to_id(cx, method_name));
+
+    bool target_has_property;
+    if (!JS_HasPropertyById(cx, target_prototype, method_name_id,
+                            &target_has_property))
+        return false;
+
+    // Don't overwrite an existing method implementation...
+    if (target_has_property)
+        return true;
+
+    JS::Rooted<JS::PropertyDescriptor> desc(cx);
+    if (!JS_GetPropertyDescriptorById(cx, prototype, method_name_id, &desc))
+        return false;
+
+    if (!desc.object()) {
+       *found = false;
+       return true;
+    }
+
+    JS::Rooted<JS::PropertyDescriptor> target_desc(cx, desc);
+    if (!JS_DefinePropertyById(cx, target_prototype, method_name_id,
+                            target_desc))
+        return false;
+
+    *found = true;
+    return true;
+}
+
 bool ObjectPrototype::resolve_no_info(JSContext* cx, JS::HandleObject obj,
                                       JS::HandleId id, bool* resolved,
                                       const char* name,
@@ -688,9 +738,15 @@ bool ObjectPrototype::resolve_no_info(JSContext* cx, JS::HandleObject obj,
             g_interface_info_find_method(iface_info, name);
         if (method_info) {
             if (g_function_info_get_flags (method_info) & GI_FUNCTION_IS_METHOD) {
-                if (!gjs_define_function(cx, obj, m_gtype, method_info))
+                bool found = false;
+                if (!copy_interface_method_to_prototype(cx, iface_info, method_info.name(),
+                                           obj, &found))
                     return false;
 
+                // Fallback to defining the function from type info...
+                if (!found && !gjs_define_function(cx, obj, m_gtype, method_info))
+                   return false;
+                
                 *resolved = true;
                 return true;
             }
@@ -876,8 +932,10 @@ bool ObjectPrototype::uncached_resolve(JSContext* context, JS::HandleObject obj,
      * introduces the iface)
      */
 
+    GjsAutoBaseInfo implementor_info;
     GjsAutoFunctionInfo method_info =
-        g_object_info_find_method_using_interfaces(m_info, name, nullptr);
+        g_object_info_find_method_using_interfaces(m_info, name,
+                                                   implementor_info.out());
 
     /**
      * Search through any interfaces implemented by the GType;
@@ -896,9 +954,20 @@ bool ObjectPrototype::uncached_resolve(JSContext* context, JS::HandleObject obj,
         gjs_debug(GJS_DEBUG_GOBJECT,
                   "Defining method %s in prototype for %s (%s.%s)",
                   method_info.name(), type_name(), ns(), this->name());
-
-        if (!gjs_define_function(context, obj, m_gtype, method_info))
+        if (GI_IS_INTERFACE_INFO(implementor_info)) {
+            bool found = false;
+            if (!copy_interface_method_to_prototype(context, implementor_info,
+                                       method_info.name(), obj, &found))
+                return false;
+            
+            // If the method was not found fallback to defining the function
+            // from type info...
+            if (!found && !gjs_define_function(context, obj, m_gtype, method_info)) {
+               return false;
+            }
+        } else if (!gjs_define_function(context, obj, m_gtype, method_info)) {
             return false;
+        }
 
         *resolved = true; /* we defined the prop in obj */
     } else {
diff --git a/installed-tests/js/testGObjectInterface.js b/installed-tests/js/testGObjectInterface.js
index 6ae08e65..34236c53 100644
--- a/installed-tests/js/testGObjectInterface.js
+++ b/installed-tests/js/testGObjectInterface.js
@@ -313,3 +313,27 @@ describe('Specific class and interface checks', function () {
         })).toThrow();
     });
 });
+
+describe('Interface prototypes', function () {
+    let dupSpy = jasmine.createSpy();
+
+    beforeAll(function () {
+        // Use .dup because we are unlikely to
+        // use it in this test.
+        //
+        // Unfortunately, overriding interface
+        // prototypes is not easily reversed.
+        Gio.File.prototype.dup = dupSpy;
+    });
+
+    it('overrides are inherited by implementing classes', function () {
+        const file = Gio.File.new_for_path('/');
+
+        expect(file).toBeInstanceOf(Gio.File);
+        expect(file).toBeInstanceOf(Gio._LocalFilePrototype.constructor);
+
+        file.dup();
+
+        expect(dupSpy).toHaveBeenCalledOnceWith();
+    });
+});


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