[gjs: 6/9] interface: Split private struct into InterfacePrototype/InterfaceInstance



commit 0fa00d49c4f063c94f225d3c8f711c41d44ee1e0
Author: Philip Chimento <philip chimento gmail com>
Date:   Fri Dec 21 17:11:21 2018 -0700

    interface: Split private struct into InterfacePrototype/InterfaceInstance
    
    This continues the ports of GObject introspection wrappers to the new
    framework introduced in wrapperutils.h. This file is a bit unusual as no
    InterfaceInstance instances should ever be constructed, so we override
    InterfaceInstance::constructor() to throw an exception.
    
    This also adds a debug topic for interfaces, since there wasn't one
    previously.

 gi/interface.cpp | 162 +++++++++++++++++--------------------------------------
 gi/interface.h   |  86 +++++++++++++++++++++++++++++
 util/log.cpp     |   3 ++
 util/log.h       |   1 +
 4 files changed, 139 insertions(+), 113 deletions(-)
---
diff --git a/gi/interface.cpp b/gi/interface.cpp
index c6c8b6ed..0d10061c 100644
--- a/gi/interface.cpp
+++ b/gi/interface.cpp
@@ -37,74 +37,36 @@
 
 #include <girepository.h>
 
-typedef struct {
-    GIInterfaceInfo *info;
-    GType gtype;
-    /* the GTypeInterface vtable wrapped by this JS Object (only used for
-       prototypes) */
-    GTypeInterface *vtable;
-} Interface;
-
-extern struct JSClass gjs_interface_class;
-
-GJS_DEFINE_PRIV_FROM_JS(Interface, gjs_interface_class)
-
-GJS_NATIVE_CONSTRUCTOR_DEFINE_ABSTRACT(interface)
-
-static void
-interface_finalize(JSFreeOp *fop,
-                   JSObject *obj)
-{
-    Interface *priv;
-
-    priv = (Interface*) JS_GetPrivate(obj);
-
-    if (priv == NULL)
-        return;
-
-    if (priv->info != NULL)
-        g_base_info_unref((GIBaseInfo*)priv->info);
-
-    g_clear_pointer(&priv->vtable, (GDestroyNotify)g_type_default_interface_unref);
+InterfacePrototype::InterfacePrototype(GIInterfaceInfo* info, GType gtype)
+    : GIWrapperPrototype(info, gtype),
+      m_vtable(
+          static_cast<GTypeInterface*>(g_type_default_interface_ref(gtype))) {
+    GJS_INC_COUNTER(interface);
+}
 
+InterfacePrototype::~InterfacePrototype(void) {
+    g_clear_pointer(&m_vtable, g_type_default_interface_unref);
     GJS_DEC_COUNTER(interface);
-    g_slice_free(Interface, priv);
 }
 
-GJS_JSAPI_RETURN_CONVENTION
-static bool
-interface_resolve(JSContext       *context,
-                  JS::HandleObject obj,
-                  JS::HandleId     id,
-                  bool            *resolved)
-{
-    Interface* priv = priv_from_js(context, obj);
-
-    if (priv == nullptr)
-        return false;
-
+// See GIWrapperBase::resolve().
+bool InterfacePrototype::resolve_impl(JSContext* context, JS::HandleObject obj,
+                                      JS::HandleId id, const char* name,
+                                      bool* resolved) {
     /* If we have no GIRepository information then this interface was defined
      * from within GJS. In that case, it has no properties that need to be
      * resolved from within C code, as interfaces cannot inherit. */
-    if (priv->info == NULL) {
-        *resolved = false;
-        return true;
-    }
-
-    JS::UniqueChars name;
-    if (!gjs_get_string_id(context, id, &name))
-        return false;
-    if (!name) {
+    if (is_custom_js_class()) {
         *resolved = false;
         return true;
     }
 
     GjsAutoFunctionInfo method_info =
-        g_interface_info_find_method(priv->info, name.get());
+        g_interface_info_find_method(m_info, name);
 
     if (method_info) {
         if (g_function_info_get_flags (method_info) & GI_FUNCTION_IS_METHOD) {
-            if (!gjs_define_function(context, obj, priv->gtype, method_info))
+            if (!gjs_define_function(context, obj, m_gtype, method_info))
                 return false;
 
             *resolved = true;
@@ -118,18 +80,16 @@ interface_resolve(JSContext       *context,
     return true;
 }
 
-GJS_JSAPI_RETURN_CONVENTION
-static bool
-interface_has_instance_func(JSContext *cx,
-                            unsigned   argc,
-                            JS::Value *vp)
-{
-    JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
-    /* This method is not called directly, so no need for error messages */
+/*
+ * InterfaceBase::has_instance:
+ *
+ * JSNative implementation of `[Symbol.hasInstance]()`. This method is never
+ * called directly, but instead is called indirectly by the JS engine as part of
+ * an `instanceof` expression.
+ */
+bool InterfaceBase::has_instance(JSContext* cx, unsigned argc, JS::Value* vp) {
+    GJS_GET_THIS(cx, argc, vp, args, interface_constructor);
 
-    JS::RootedValue interface(cx, args.computeThis(cx));
-    g_assert(interface.isObject());
-    JS::RootedObject interface_constructor(cx, &interface.toObject());
     JS::RootedObject interface_proto(cx);
     const GjsAtoms& atoms = GjsContextPrivate::atoms(cx);
     if (!gjs_object_require_property(cx, interface_constructor,
@@ -137,45 +97,47 @@ interface_has_instance_func(JSContext *cx,
                                      &interface_proto))
         return false;
 
-    Interface *priv;
-    if (!priv_from_js_with_typecheck(cx, interface_proto, &priv))
+    InterfaceBase* priv = InterfaceBase::for_js_typecheck(cx, interface_proto);
+    if (!priv)
         return false;
 
+    return priv->to_prototype()->has_instance_impl(cx, args);
+}
+
+// See InterfaceBase::has_instance().
+bool InterfacePrototype::has_instance_impl(JSContext* cx,
+                                           const JS::CallArgs& args) {
+    // This method is never called directly, so no need for error messages.
     g_assert(args.length() == 1);
     g_assert(args[0].isObject());
     JS::RootedObject instance(cx, &args[0].toObject());
-    bool isinstance = gjs_typecheck_object(cx, instance, priv->gtype, false);
+    bool isinstance = gjs_typecheck_object(cx, instance, m_gtype, false);
     args.rval().setBoolean(isinstance);
     return true;
 }
 
-static const struct JSClassOps gjs_interface_class_ops = {
+// clang-format off
+const struct JSClassOps InterfaceBase::class_ops = {
     nullptr,  // addProperty
     nullptr,  // deleteProperty
     nullptr,  // enumerate
     nullptr,  // newEnumerate
-    interface_resolve,
+    &InterfaceBase::resolve,
     nullptr,  // mayResolve
-    interface_finalize};
+    &InterfaceBase::finalize,
+};
 
-struct JSClass gjs_interface_class = {
+const struct JSClass InterfaceBase::klass = {
     "GObject_Interface",
     JSCLASS_HAS_PRIVATE | JSCLASS_BACKGROUND_FINALIZE,
-    &gjs_interface_class_ops
-};
-
-JSPropertySpec gjs_interface_proto_props[] = {
-    JS_PS_END
+    &InterfaceBase::class_ops
 };
 
-JSFunctionSpec gjs_interface_proto_funcs[] = {
-    JS_FS_END
-};
-
-JSFunctionSpec gjs_interface_static_funcs[] = {
-    JS_SYM_FN(hasInstance, interface_has_instance_func, 1, 0),
+JSFunctionSpec InterfaceBase::static_methods[] = {
+    JS_SYM_FN(hasInstance, &InterfaceBase::has_instance, 1, 0),
     JS_FS_END
 };
+// clang-format on
 
 bool
 gjs_define_interface_class(JSContext              *context,
@@ -184,47 +146,21 @@ gjs_define_interface_class(JSContext              *context,
                            GType                   gtype,
                            JS::MutableHandleObject constructor)
 {
-    Interface *priv;
-    const char *constructor_name;
-    const char *ns;
     JS::RootedObject prototype(context);
 
-    ns = gjs_get_names_from_gtype_and_gi_info(gtype, (GIBaseInfo *) info,
-                                              &constructor_name);
-
-    if (!gjs_init_class_dynamic(context, in_object, nullptr, ns,
-                                constructor_name,
-                                &gjs_interface_class,
-                                gjs_interface_constructor, 0,
-                                /* props of prototype */
-                                &gjs_interface_proto_props[0],
-                                /* funcs of prototype */
-                                &gjs_interface_proto_funcs[0],
-                                /* props of constructor, MyConstructor.myprop */
-                                NULL,
-                                /* funcs of constructor, MyConstructor.myfunc() */
-                                gjs_interface_static_funcs,
-                                &prototype,
-                                constructor)) {
+    if (!InterfacePrototype::create_class(context, in_object, info, gtype,
+                                          constructor, &prototype))
         return false;
-    }
-
-    GJS_INC_COUNTER(interface);
-    priv = g_slice_new0(Interface);
-    priv->info = info == NULL ? NULL : g_base_info_ref((GIBaseInfo *) info);
-    priv->gtype = gtype;
-    priv->vtable = (GTypeInterface *) g_type_default_interface_ref(gtype);
-    JS_SetPrivate(prototype, priv);
 
     /* If we have no GIRepository information, then this interface was defined
      * from within GJS and therefore has no C static methods to be defined. */
-    if (priv->info) {
+    if (info) {
         if (!gjs_define_static_methods<InfoType::Interface>(
-                context, constructor, priv->gtype, priv->info))
+                context, constructor, gtype, info))
             return false;
     }
 
-    return gjs_wrapper_define_gtype_prop(context, constructor, priv->gtype);
+    return true;
 }
 
 bool
diff --git a/gi/interface.h b/gi/interface.h
index d34d7862..e91197ef 100644
--- a/gi/interface.h
+++ b/gi/interface.h
@@ -29,9 +29,95 @@
 #include <glib.h>
 #include <girepository.h>
 
+#include "gi/wrapperutils.h"
 #include "gjs/jsapi-util.h"
 #include "gjs/macros.h"
 
+class InterfacePrototype;
+class InterfaceInstance;
+
+/* For more information on this Base/Prototype/Interface scheme, see the notes
+ * in wrapperutils.h.
+ *
+ * What's unusual about this subclass is that InterfaceInstance should never
+ * actually be instantiated. Interfaces can't be constructed, and
+ * GIWrapperBase::constructor() is overridden to just throw an exception and not
+ * create any JS wrapper object.
+ *
+ * We use the template classes from wrapperutils.h anyway, because there is
+ * still a lot of common code.
+ */
+
+class InterfaceBase : public GIWrapperBase<InterfaceBase, InterfacePrototype,
+                                           InterfaceInstance> {
+    friend class GIWrapperBase;
+
+ protected:
+    explicit InterfaceBase(InterfacePrototype* proto = nullptr)
+        : GIWrapperBase(proto) {}
+    ~InterfaceBase(void) {}
+
+    static const GjsDebugTopic debug_topic = GJS_DEBUG_GINTERFACE;
+    static constexpr const char* debug_tag = "GInterface";
+
+    static const struct JSClassOps class_ops;
+    static const struct JSClass klass;
+    static JSFunctionSpec static_methods[];
+
+    GJS_USE const char* to_string_kind(void) const { return "interface"; }
+
+    // JSNative methods
+
+    // Overrides GIWrapperBase::constructor().
+    GJS_JSAPI_RETURN_CONVENTION
+    static bool constructor(JSContext* cx, unsigned argc, JS::Value* vp) {
+        JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+        gjs_throw_abstract_constructor_error(cx, args);
+        return false;
+    }
+
+    GJS_JSAPI_RETURN_CONVENTION
+    static bool has_instance(JSContext* cx, unsigned argc, JS::Value* vp);
+};
+
+class InterfacePrototype
+    : public GIWrapperPrototype<InterfaceBase, InterfacePrototype,
+                                InterfaceInstance, GIInterfaceInfo> {
+    friend class GIWrapperPrototype;
+    friend class GIWrapperBase;
+    friend class InterfaceBase;  // for has_instance_impl
+
+    // the GTypeInterface vtable wrapped by this JS object
+    GTypeInterface* m_vtable;
+
+    explicit InterfacePrototype(GIInterfaceInfo* info, GType gtype);
+    ~InterfacePrototype(void);
+
+    // JSClass operations
+
+    GJS_JSAPI_RETURN_CONVENTION
+    bool resolve_impl(JSContext* cx, JS::HandleObject obj, JS::HandleId id,
+                      const char* name, bool* resolved);
+
+    // JS methods
+
+    GJS_JSAPI_RETURN_CONVENTION
+    bool has_instance_impl(JSContext* cx, const JS::CallArgs& args);
+};
+
+class InterfaceInstance
+    : public GIWrapperInstance<InterfaceBase, InterfacePrototype,
+                               InterfaceInstance> {
+    friend class GIWrapperInstance;
+    friend class GIWrapperBase;
+
+    G_GNUC_NORETURN InterfaceInstance(JSContext* cx, JS::HandleObject obj)
+        : GIWrapperInstance(cx, obj) {
+        g_assert_not_reached();
+    }
+    G_GNUC_NORETURN ~InterfaceInstance(void) { g_assert_not_reached(); }
+};
+
 G_BEGIN_DECLS
 
 GJS_JSAPI_RETURN_CONVENTION
diff --git a/util/log.cpp b/util/log.cpp
index 213be8ea..410913e4 100644
--- a/util/log.cpp
+++ b/util/log.cpp
@@ -221,6 +221,9 @@ _Pragma("GCC diagnostic pop")
     case GJS_DEBUG_GERROR:
         prefix = "JS G ERR";
         break;
+    case GJS_DEBUG_GINTERFACE:
+        prefix = "JS G IFACE";
+        break;
     default:
         prefix = "???";
         break;
diff --git a/util/log.h b/util/log.h
index 00b9d458..a88b8786 100644
--- a/util/log.h
+++ b/util/log.h
@@ -50,6 +50,7 @@ typedef enum {
     GJS_DEBUG_GPARAM,
     GJS_DEBUG_GERROR,
     GJS_DEBUG_GFUNDAMENTAL,
+    GJS_DEBUG_GINTERFACE,
 } GjsDebugTopic;
 
 /* These defines are because we have some pretty expensive and


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