[gjs: 2/3] object: Support fields defined in ancestor classes



commit 9f182bd6e8b23f7d3c15d70fd7a09cf3bb529e2b
Author: Philip Chimento <philip chimento gmail com>
Date:   Sun Feb 10 21:44:27 2019 -0800

    object: Support fields defined in ancestor classes
    
    We define a JS property with getter and setter for a GObject field in
    resolve_impl() (where lazy properties are defined) when JS code tries to
    access a field. The getter and setter retrieve the field's GIFieldInfo
    information from a cache, using a key that is built into the getter and
    setter.
    
    Because this is done during the resolve operation, and the resolve
    operation can occur multiple times going up the prototype chain, the JS
    property may end up on a prototype that is an ancestor of the object's
    direct prototype. So the GIFieldInfo may end up on any prototype in the
    prototype chain, and so we have to search through the prototype chain
    for it.
    
    Closes: #223.

 gi/object.cpp                                    | 51 ++++++++++++++++++------
 installed-tests/js/testEverythingEncapsulated.js | 13 ++++++
 2 files changed, 51 insertions(+), 13 deletions(-)
---
diff --git a/gi/object.cpp b/gi/object.cpp
index fb53e6f1..52d178a8 100644
--- a/gi/object.cpp
+++ b/gi/object.cpp
@@ -375,19 +375,6 @@ static GjsAutoFieldInfo lookup_field_info(GIObjectInfo* info,
     return retval;
 }
 
-// Retrieves a GIFieldInfo for a field named @key. This is for use in
-// field_getter_impl() and field_setter_impl(), where the field info *must* have
-// been cached previously in resolve_impl(). This will fail an assertion if
-// there is no cached field info.
-//
-// The caller does not own the return value, and it can never be null.
-GIFieldInfo* ObjectPrototype::lookup_cached_field_info(JSContext* cx,
-                                                       JS::HandleString key) {
-    auto entry = m_field_cache.lookupForAdd(key);
-    g_assert(entry && "Field info should have been cached in resolve_impl()");
-    return entry->value().get();
-}
-
 bool ObjectBase::field_getter(JSContext* cx, unsigned argc, JS::Value* vp) {
     GJS_GET_WRAPPER_PRIV(cx, argc, vp, args, obj, ObjectBase, priv);
 
@@ -1668,6 +1655,44 @@ gjs_lookup_object_prototype(JSContext *context,
     return gjs_lookup_object_prototype_from_info(context, info, gtype);
 }
 
+// Retrieves a GIFieldInfo for a field named @key. This is for use in
+// field_getter_impl() and field_setter_impl(), where the field info *must* have
+// been cached previously in resolve_impl() on this ObjectPrototype or one of
+// its parent ObjectPrototypes. This will fail an assertion if there is no
+// cached field info.
+//
+// The caller does not own the return value, and it can never be null.
+GIFieldInfo* ObjectPrototype::lookup_cached_field_info(JSContext* cx,
+                                                       JS::HandleString key) {
+    if (is_custom_js_class()) {
+        // Custom JS classes can't have fields. We must be looking up a field on
+        // a GObject-introspected parent.
+        GType parent_gtype = g_type_parent(m_gtype);
+        g_assert(parent_gtype != G_TYPE_INVALID &&
+                 "Custom JS class must have parent");
+        ObjectPrototype* parent_proto =
+            ObjectPrototype::for_gtype(parent_gtype);
+        g_assert(parent_proto &&
+                 "Custom JS class's parent must have been accessed in JS");
+        return parent_proto->lookup_cached_field_info(cx, key);
+    }
+
+    gjs_debug_jsprop(GJS_DEBUG_GOBJECT,
+                     "Looking up cached field info for '%s' in '%s' prototype",
+                     gjs_debug_string(key).c_str(), g_type_name(m_gtype));
+    auto entry = m_field_cache.lookupForAdd(key);
+    if (entry)
+        return entry->value().get();
+
+    // We must be looking up a field defined on a parent. Look up the prototype
+    // object via its GIObjectInfo.
+    GjsAutoObjectInfo parent_info = g_object_info_get_parent(m_info);
+    JS::RootedObject parent_proto(cx, gjs_lookup_object_prototype_from_info(
+                                          cx, parent_info, G_TYPE_INVALID));
+    ObjectPrototype* parent = ObjectPrototype::for_js(cx, parent_proto);
+    return parent->lookup_cached_field_info(cx, key);
+}
+
 void ObjectBase::associate_closure(JSContext* cx, GClosure* closure) {
     if (!is_prototype())
         to_instance()->ensure_uses_toggle_ref(cx);
diff --git a/installed-tests/js/testEverythingEncapsulated.js 
b/installed-tests/js/testEverythingEncapsulated.js
index b24d5cba..9e83253e 100644
--- a/installed-tests/js/testEverythingEncapsulated.js
+++ b/installed-tests/js/testEverythingEncapsulated.js
@@ -243,6 +243,19 @@ describe('Introspected GObject', function () {
     it('gives undefined for write-only properties', function () {
         expect(obj.write_only).not.toBeDefined();
     });
+
+    it('can read fields from a parent class', function () {
+        let subobj = new Regress.TestSubObj({
+            int: 42,
+            float: 3.1416,
+            double: 2.71828,
+        });
+
+        // see "can access fields with simple types" above
+        expect(subobj.some_int8).toEqual(subobj.int);
+        expect(subobj.some_float).toEqual(subobj.float);
+        expect(subobj.some_double).toEqual(subobj.double);
+    });
 });
 
 describe('Introspected function length', function () {


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