LCOV - code coverage report
Current view: top level - gi - cwrapper.h (source / functions) Hit Total Coverage
Test: gjs- Code Coverage Lines: 131 148 88.5 %
Date: 2023-09-17 02:39:54 Functions: 165 204 80.9 %
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 39 66 59.1 %

           Branch data     Line data    Source code
       1                 :            : /* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
       2                 :            : // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later
       3                 :            : // SPDX-FileCopyrightText: 2020 Philip Chimento <philip.chimento@gmail.com>
       4                 :            : 
       5                 :            : #pragma once
       6                 :            : 
       7                 :            : #include <config.h>
       8                 :            : 
       9                 :            : #include <assert.h>
      10                 :            : #include <stddef.h>  // for size_t
      11                 :            : 
      12                 :            : #include <type_traits>  // for integral_constant
      13                 :            : 
      14                 :            : #include <glib-object.h>  // for GType
      15                 :            : 
      16                 :            : #include <js/CallArgs.h>
      17                 :            : #include <js/Class.h>
      18                 :            : #include <js/ErrorReport.h>  // for JSEXN_TYPEERR
      19                 :            : #include <js/GlobalObject.h>  // for CurrentGlobalOrNull
      20                 :            : #include <js/Id.h>
      21                 :            : #include <js/Object.h>  // for GetClass
      22                 :            : #include <js/PropertyAndElement.h>
      23                 :            : #include <js/RootingAPI.h>
      24                 :            : #include <js/TypeDecls.h>
      25                 :            : #include <js/Value.h>
      26                 :            : #include <jsapi.h>  // for JSFUN_CONSTRUCTOR, JS_NewPlainObject, JS_GetFuncti...
      27                 :            : #include <jspubtd.h>  // for JSProto_Object, JSProtoKey
      28                 :            : 
      29                 :            : #include "gjs/jsapi-util.h"
      30                 :            : #include "gjs/macros.h"
      31                 :            : #include "util/log.h"
      32                 :            : 
      33                 :            : struct JSFunctionSpec;
      34                 :            : struct JSPropertySpec;
      35                 :            : 
      36                 :            : // gi/cwrapper.h - template implementing a JS object that wraps a C pointer.
      37                 :            : // This template is used for many of the special objects in GJS. It contains
      38                 :            : // functionality such as storing the class's prototype in a global slot, where
      39                 :            : // it can be easily retrieved in order to create new objects.
      40                 :            : 
      41                 :            : /*
      42                 :            :  * GJS_CHECK_WRAPPER_PRIV:
      43                 :            :  * @cx: JSContext pointer passed into JSNative function
      44                 :            :  * @argc: Number of arguments passed into JSNative function
      45                 :            :  * @vp: Argument value array passed into JSNative function
      46                 :            :  * @args: Name for JS::CallArgs variable defined by this code snippet
      47                 :            :  * @thisobj: Name for JS::RootedObject variable referring to function's this
      48                 :            :  * @type: Type of private data
      49                 :            :  * @priv: Name for private data variable defined by this code snippet
      50                 :            :  *
      51                 :            :  * A convenience macro for getting the private data from GJS classes using
      52                 :            :  * CWrapper or GIWrapper.
      53                 :            :  * Throws an error and returns false if the 'this' object is not the right type.
      54                 :            :  * Use in any JSNative function.
      55                 :            :  */
      56                 :            : #define GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, args, thisobj, type, priv) \
      57                 :            :     GJS_GET_THIS(cx, argc, vp, args, thisobj);                          \
      58                 :            :     type* priv;                                                         \
      59                 :            :     if (!type::for_js_typecheck(cx, thisobj, &priv, &args))             \
      60                 :            :         return false;
      61                 :            : 
      62                 :            : GJS_JSAPI_RETURN_CONVENTION
      63                 :            : bool gjs_wrapper_define_gtype_prop(JSContext* cx, JS::HandleObject constructor,
      64                 :            :                                    GType gtype);
      65                 :            : 
      66                 :            : /*
      67                 :            :  * CWrapperPointerOps:
      68                 :            :  *
      69                 :            :  * This class contains methods that are common to both CWrapper and
      70                 :            :  * GIWrapperBase, for retrieving the wrapped C pointer out of the JS object.
      71                 :            :  */
      72                 :            : template <class Base, typename Wrapped = Base>
      73                 :            : class CWrapperPointerOps {
      74                 :            :  public:
      75                 :            :     /*
      76                 :            :      * CWrapperPointerOps::for_js:
      77                 :            :      *
      78                 :            :      * Gets the wrapped C pointer belonging to a particular JS object wrapper.
      79                 :            :      * Checks that the wrapper object has the right JSClass (Base::klass).
      80                 :            :      * A null return value means either that the object didn't have the right
      81                 :            :      * class, or that no private data has been set yet on the wrapper. To
      82                 :            :      * distinguish between these two cases, use for_js_typecheck().
      83                 :            :      */
      84                 :     111805 :     [[nodiscard]] static Wrapped* for_js(JSContext* cx,
      85                 :            :                                          JS::HandleObject wrapper) {
      86         [ +  + ]:     111805 :         if (!JS_InstanceOf(cx, wrapper, &Base::klass, nullptr))
      87                 :       1203 :             return nullptr;
      88                 :            : 
      89                 :     110602 :         return JS::GetMaybePtrFromReservedSlot<Wrapped>(wrapper, POINTER);
      90                 :            :     }
      91                 :            : 
      92                 :            :     /*
      93                 :            :      * CWrapperPointerOps::typecheck:
      94                 :            :      *
      95                 :            :      * Checks if the given wrapper object has the right JSClass (Base::klass).
      96                 :            :      */
      97                 :     158948 :     [[nodiscard]] static bool typecheck(JSContext* cx, JS::HandleObject wrapper,
      98                 :            :                                         JS::CallArgs* args = nullptr) {
      99                 :     158948 :         return JS_InstanceOf(cx, wrapper, &Base::klass, args);
     100                 :            :     }
     101                 :            : 
     102                 :            :     /*
     103                 :            :      * CWrapperPointerOps::for_js_typecheck:
     104                 :            :      *
     105                 :            :      * Like for_js(), only throws a JS exception if the wrapper object has the
     106                 :            :      * wrong class. Use in JSNative functions, where you have access to a
     107                 :            :      * JS::CallArgs. The exception message will mention args.callee.
     108                 :            :      *
     109                 :            :      * The second overload can be used when you don't have access to an
     110                 :            :      * instance of JS::CallArgs. The exception message will be generic.
     111                 :            :      */
     112                 :            :     GJS_JSAPI_RETURN_CONVENTION
     113                 :      65684 :     static bool for_js_typecheck(JSContext* cx, JS::HandleObject wrapper,
     114                 :            :                                  Wrapped** out, JS::CallArgs* args) {
     115         [ +  + ]:      65684 :         if (!typecheck(cx, wrapper, args))
     116                 :          2 :             return false;
     117                 :      65682 :         *out = for_js_nocheck(wrapper);
     118                 :      65682 :         return true;
     119                 :            :     }
     120                 :            :     GJS_JSAPI_RETURN_CONVENTION
     121                 :      93264 :     static bool for_js_typecheck(JSContext* cx, JS::HandleObject wrapper,
     122                 :            :                                  Wrapped** out) {
     123         [ +  + ]:      93264 :         if (!typecheck(cx, wrapper)) {
     124                 :          5 :             const JSClass* obj_class = JS::GetClass(wrapper);
     125                 :          5 :             gjs_throw_custom(cx, JSEXN_TYPEERR, nullptr,
     126                 :            :                              "Object %p is not a subclass of %s, it's a %s",
     127                 :          5 :                              wrapper.get(), Base::klass.name, obj_class->name);
     128                 :          5 :             return false;
     129                 :            :         }
     130                 :      93259 :         *out = for_js_nocheck(wrapper);
     131                 :      93259 :         return true;
     132                 :            :     }
     133                 :            : 
     134                 :            :     /*
     135                 :            :      * CWrapperPointerOps::for_js_nocheck:
     136                 :            :      *
     137                 :            :      * Use when you don't have a JSContext* available. This method is infallible
     138                 :            :      * and cannot trigger a GC, so it's safe to use from finalize() and trace().
     139                 :            :      * (It can return null if no private data has been set yet on the wrapper.)
     140                 :            :      */
     141                 :     196366 :     [[nodiscard]] static Wrapped* for_js_nocheck(JSObject* wrapper) {
     142                 :     196366 :         return JS::GetMaybePtrFromReservedSlot<Wrapped>(wrapper, POINTER);
     143                 :            :     }
     144                 :            : 
     145                 :            :  protected:
     146                 :            :     // The first reserved slot always stores the private pointer.
     147                 :            :     static const size_t POINTER = 0;
     148                 :            : 
     149                 :            :     /*
     150                 :            :      * CWrapperPointerOps::has_private:
     151                 :            :      *
     152                 :            :      * Returns true if a private C pointer has already been associated with the
     153                 :            :      * wrapper object.
     154                 :            :      */
     155                 :      33672 :     [[nodiscard]] static bool has_private(JSObject* wrapper) {
     156                 :      33672 :         return !!JS::GetMaybePtrFromReservedSlot<Wrapped>(wrapper, POINTER);
     157                 :            :     }
     158                 :            : 
     159                 :            :     /*
     160                 :            :      * CWrapperPointerOps::init_private:
     161                 :            :      *
     162                 :            :      * Call this to initialize the wrapper object's private C pointer. The
     163                 :            :      * pointer should not be null. This should not be called twice, without
     164                 :            :      * calling unset_private() in between.
     165                 :            :      */
     166                 :      33672 :     static void init_private(JSObject* wrapper, Wrapped* ptr) {
     167         [ +  - ]:      33672 :         assert(!has_private(wrapper) &&
     168                 :            :                "wrapper object should be a fresh object");
     169         [ -  + ]:      33672 :         assert(ptr && "private pointer should not be null, use unset_private");
     170                 :      33672 :         JS::SetReservedSlot(wrapper, POINTER, JS::PrivateValue(ptr));
     171                 :      33672 :     }
     172                 :            : 
     173                 :            :     /*
     174                 :            :      * CWrapperPointerOps::unset_private:
     175                 :            :      *
     176                 :            :      * Call this to remove the wrapper object's private C pointer. After calling
     177                 :            :      * this, it's okay to call init_private() again.
     178                 :            :      */
     179                 :      33473 :     static void unset_private(JSObject* wrapper) {
     180                 :      33473 :         JS::SetReservedSlot(wrapper, POINTER, JS::UndefinedValue());
     181                 :      33473 :     }
     182                 :            : };
     183                 :            : 
     184                 :            : /*
     185                 :            :  * CWrapper:
     186                 :            :  *
     187                 :            :  * This template implements a JS object that wraps a C pointer, stores its
     188                 :            :  * prototype in a global slot, and includes some optional functionality.
     189                 :            :  *
     190                 :            :  * If you derive from this class, you must implement:
     191                 :            :  *  - static constexpr GjsGlobalSlot PROTOTYPE_SLOT: global slot that the
     192                 :            :  *    prototype will be stored in
     193                 :            :  *  - static constexpr GjsDebugTopic DEBUG_TOPIC: debug log domain
     194                 :            :  *  - static constexpr JSClass klass: see documentation in SpiderMonkey; the
     195                 :            :  *    class may have JSClassOps (see below under CWrapper::class_ops) but must
     196                 :            :  *    at least have its js::ClassSpec member set. The members of js::ClassSpec
     197                 :            :  *    are createConstructor, createPrototype, constructorFunctions,
     198                 :            :  *    constructorProperties, prototypeFunctions, prototypeProperties,
     199                 :            :  *    finishInit, and flags.
     200                 :            :  *  - static Wrapped* constructor_impl(JSContext*, const JS::CallArgs&): custom
     201                 :            :  *    constructor functionality. If your JS object doesn't need a constructor
     202                 :            :  *    (i.e. user code can't use the `new` operator on it) then you can skip this
     203                 :            :  *    one, and include js::ClassSpec::DontDefineConstructor in your
     204                 :            :  *    class_spec's flags member.
     205                 :            :  *  - static constexpr unsigned constructor_nargs: number of arguments that the
     206                 :            :  *    constructor takes. If you implement constructor_impl() then also add this.
     207                 :            :  *  - void finalize_impl(JS::GCContext*, Wrapped*): called when the JS object is
     208                 :            :  *    garbage collected, use this to free the C pointer and do any other cleanup
     209                 :            :  *
     210                 :            :  * Add optional functionality by setting members of class_spec:
     211                 :            :  *  - createConstructor: the default is to create a constructor function that
     212                 :            :  *    calls constructor_impl(), unless flags includes DontDefineConstructor. If
     213                 :            :  *    you need something else, set this member.
     214                 :            :  *  - createPrototype: the default is to use a plain object as the prototype. If
     215                 :            :  *    you need something else, set this member.
     216                 :            :  *  - constructorFunctions: If the class has static methods, set this member.
     217                 :            :  *  - constructorProperties: If the class has static properties, set this
     218                 :            :  *    member.
     219                 :            :  *  - prototypeFunctions: If the class has methods, set this member.
     220                 :            :  *  - prototypeProperties: If the class has properties, set this member.
     221                 :            :  *  - finishInit: If you need to do any other initialization on the prototype or
     222                 :            :  *    the constructor object, set this member.
     223                 :            :  *  - flags: Specify DontDefineConstructor here if you don't want a user-visible
     224                 :            :  *    constructor.
     225                 :            :  *
     226                 :            :  * You may override CWrapper::class_ops if you want to opt in to more JSClass
     227                 :            :  * operations. In that case, CWrapper includes some optional functionality:
     228                 :            :  *  - resolve: include &resolve in your class_ops, and implement
     229                 :            :  *    bool resolve_impl(JSContext*, JS::HandleObject, JS::HandleId, bool*).
     230                 :            :  *  - new enumerate: include &new_enumerate in your class_ops, and implement
     231                 :            :  *    bool new_enumerate_impl(JSContext*, JS::HandleObject,
     232                 :            :  *    JS::MutableHandleIdVector, bool).
     233                 :            :  *
     234                 :            :  * This template uses the Curiously Recurring Template Pattern (CRTP), which
     235                 :            :  * requires inheriting classes to declare themselves friends of the parent
     236                 :            :  * class, so that the parent class can call their private methods.
     237                 :            :  *
     238                 :            :  * For more information about the CRTP, the Wikipedia article is informative:
     239                 :            :  * https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern
     240                 :            :  */
     241                 :            : template <class Base, typename Wrapped = Base>
     242                 :            : class CWrapper : public CWrapperPointerOps<Base, Wrapped> {
     243                 :            :     GJS_JSAPI_RETURN_CONVENTION
     244                 :         95 :     static bool constructor(JSContext* cx, unsigned argc, JS::Value* vp) {
     245                 :         95 :         JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
     246                 :            : 
     247         [ -  + ]:         95 :         if (!args.isConstructing()) {
     248                 :          0 :             gjs_throw_constructor_error(cx);
     249                 :          0 :             return false;
     250                 :            :         }
     251                 :         95 :         JS::RootedObject object(
     252                 :         95 :             cx, JS_NewObjectForConstructor(cx, &Base::klass, args));
     253         [ -  + ]:         95 :         if (!object)
     254                 :          0 :             return false;
     255                 :            : 
     256                 :         95 :         Wrapped* priv = Base::constructor_impl(cx, args);
     257         [ +  + ]:         95 :         if (!priv)
     258                 :          2 :             return false;
     259                 :         93 :         CWrapperPointerOps<Base, Wrapped>::init_private(object, priv);
     260                 :            : 
     261                 :         93 :         args.rval().setObject(*object);
     262                 :         93 :         return true;
     263                 :         95 :     }
     264                 :            : 
     265                 :            :     GJS_JSAPI_RETURN_CONVENTION
     266                 :          0 :     static bool abstract_constructor(JSContext* cx, unsigned argc,
     267                 :            :                                      JS::Value* vp) {
     268                 :          0 :         JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
     269                 :          0 :         gjs_throw_abstract_constructor_error(cx, args);
     270                 :          0 :         return false;
     271                 :            :     }
     272                 :            : 
     273                 :            :     // Debug methods, no-op unless verbose logging is compiled in
     274                 :            : 
     275                 :            :  protected:
     276                 :      22042 :     static void debug_lifecycle(
     277                 :            :         const void* wrapped_ptr GJS_USED_VERBOSE_LIFECYCLE,
     278                 :            :         const void* obj GJS_USED_VERBOSE_LIFECYCLE,
     279                 :            :         const char* message GJS_USED_VERBOSE_LIFECYCLE) {
     280                 :            :         gjs_debug_lifecycle(Base::DEBUG_TOPIC, "[%p: JS wrapper %p] %s",
     281                 :            :                             wrapped_ptr, obj, message);
     282                 :      22042 :     }
     283                 :          2 :     void debug_jsprop(const char* message GJS_USED_VERBOSE_PROPS,
     284                 :            :                       const char* id GJS_USED_VERBOSE_PROPS,
     285                 :            :                       const void* obj GJS_USED_VERBOSE_PROPS) const {
     286                 :            :         gjs_debug_jsprop(Base::DEBUG_TOPIC, "[%p: JS wrapper %p] %s prop %s",
     287                 :            :                          this, obj, message, id);
     288                 :          2 :     }
     289                 :      10632 :     void debug_jsprop(const char* message, jsid id, const void* obj) const {
     290                 :            :         if constexpr (GJS_VERBOSE_ENABLE_PROPS)
     291                 :            :             debug_jsprop(message, gjs_debug_id(id).c_str(), obj);
     292                 :      10632 :     }
     293                 :            : 
     294                 :      12418 :     static void finalize(JS::GCContext* gcx, JSObject* obj) {
     295                 :      12418 :         Wrapped* priv = Base::for_js_nocheck(obj);
     296                 :            : 
     297                 :            :         // Call only CWrapper's original method here, not any overrides; e.g.,
     298                 :            :         // we don't want to deal with a read barrier.
     299                 :      12418 :         CWrapper::debug_lifecycle(priv, obj, "Finalize");
     300                 :            : 
     301                 :      12418 :         Base::finalize_impl(gcx, priv);
     302                 :            : 
     303                 :      12418 :         CWrapperPointerOps<Base, Wrapped>::unset_private(obj);
     304                 :      12418 :     }
     305                 :            : 
     306                 :            :     static constexpr JSClassOps class_ops = {
     307                 :            :         nullptr,  // addProperty
     308                 :            :         nullptr,  // deleteProperty
     309                 :            :         nullptr,  // enumerate
     310                 :            :         nullptr,  // newEnumerate
     311                 :            :         nullptr,  // resolve
     312                 :            :         nullptr,  // mayResolve
     313                 :            :         &CWrapper::finalize,
     314                 :            :     };
     315                 :            : 
     316                 :            :     /*
     317                 :            :      * CWrapper::create_abstract_constructor:
     318                 :            :      *
     319                 :            :      * This function can be used as the createConstructor member of class_ops.
     320                 :            :      * It creates a constructor that always throws if it is the new.target. Use
     321                 :            :      * it if you do need a constructor object to exist (for example, if it has
     322                 :            :      * static methods) but you don't want it to be able to be called.
     323                 :            :      */
     324                 :            :     GJS_JSAPI_RETURN_CONVENTION
     325                 :         10 :     static JSObject* create_abstract_constructor(JSContext* cx, JSProtoKey) {
     326                 :         10 :         return JS_GetFunctionObject(
     327                 :            :             JS_NewFunction(cx, &Base::abstract_constructor, 0,
     328                 :         20 :                            JSFUN_CONSTRUCTOR, Base::klass.name));
     329                 :            :     }
     330                 :            : 
     331                 :            :     /*
     332                 :            :      * CWrapper::define_gtype_prop:
     333                 :            :      *
     334                 :            :      * This function can be used as the finishInit member of class_ops. It
     335                 :            :      * defines a '$gtype' property on the constructor. If you use it, you must
     336                 :            :      * implement a gtype() static method that returns the GType to define.
     337                 :            :      */
     338                 :            :     GJS_JSAPI_RETURN_CONVENTION
     339                 :         26 :     static bool define_gtype_prop(JSContext* cx, JS::HandleObject ctor,
     340                 :            :                                   JS::HandleObject proto [[maybe_unused]]) {
     341                 :         26 :         return gjs_wrapper_define_gtype_prop(cx, ctor, Base::gtype());
     342                 :            :     }
     343                 :            : 
     344                 :            :     // Used to get the prototype when it is guaranteed to have already been
     345                 :            :     // created
     346                 :            :     GJS_JSAPI_RETURN_CONVENTION
     347                 :        121 :     static JSObject* prototype(JSContext* cx) {
     348                 :        121 :         JSObject* global = JS::CurrentGlobalOrNull(cx);
     349         [ -  + ]:        121 :         assert(global && "Must be in a realm to call prototype()");
     350                 :        121 :         JS::RootedValue v_proto(
     351                 :        121 :             cx, gjs_get_global_slot(global, Base::PROTOTYPE_SLOT));
     352         [ +  - ]:        121 :         assert(!v_proto.isUndefined() &&
     353                 :            :                "create_prototype() must be called before prototype()");
     354         [ +  - ]:        121 :         assert(v_proto.isObject() &&
     355                 :            :                "Someone stored some weird value in a global slot");
     356                 :        121 :         return &v_proto.toObject();
     357                 :        121 :     }
     358                 :            : 
     359                 :            :     GJS_JSAPI_RETURN_CONVENTION
     360                 :      10632 :     static bool resolve(JSContext* cx, JS::HandleObject obj, JS::HandleId id,
     361                 :            :                         bool* resolved) {
     362                 :      10632 :         Wrapped* priv = CWrapperPointerOps<Base, Wrapped>::for_js(cx, obj);
     363         [ -  + ]:      10632 :         assert(priv && "resolve called on wrong object");
     364                 :      10632 :         priv->debug_jsprop("Resolve hook", id, obj);
     365                 :      10632 :         return priv->resolve_impl(cx, obj, id, resolved);
     366                 :            :     }
     367                 :            : 
     368                 :            :     GJS_JSAPI_RETURN_CONVENTION
     369                 :          2 :     static bool new_enumerate(JSContext* cx, JS::HandleObject obj,
     370                 :            :                               JS::MutableHandleIdVector properties,
     371                 :            :                               bool only_enumerable) {
     372                 :          2 :         Wrapped* priv = CWrapperPointerOps<Base, Wrapped>::for_js(cx, obj);
     373         [ -  + ]:          2 :         assert(priv && "enumerate called on wrong object");
     374                 :          2 :         priv->debug_jsprop("Enumerate hook", "(all)", obj);
     375                 :          2 :         return priv->new_enumerate_impl(cx, obj, properties, only_enumerable);
     376                 :            :     }
     377                 :            : 
     378                 :            :  public:
     379                 :            :     /*
     380                 :            :      * CWrapper::create_prototype:
     381                 :            :      * @module: Object on which to define the constructor as a property, or
     382                 :            :      *   the global object if not given
     383                 :            :      *
     384                 :            :      * Create the class's prototype and store it in the global slot, or
     385                 :            :      * retrieve it if it has already been created.
     386                 :            :      *
     387                 :            :      * Unless DontDefineConstructor is in class_ops.flags, also create the
     388                 :            :      * class's constructor, and define it as a property on @module.
     389                 :            :      */
     390                 :            :     GJS_JSAPI_RETURN_CONVENTION
     391                 :      12512 :     static JSObject* create_prototype(JSContext* cx,
     392                 :            :                                       JS::HandleObject module = nullptr) {
     393                 :      12512 :         JSObject* global = JS::CurrentGlobalOrNull(cx);
     394         [ -  + ]:      12512 :         assert(global && "Must be in a realm to call create_prototype()");
     395                 :            : 
     396                 :            :         // If we've been here more than once, we already have the proto
     397                 :      12512 :         JS::RootedValue v_proto(
     398                 :      12512 :             cx, gjs_get_global_slot(global, Base::PROTOTYPE_SLOT));
     399         [ +  + ]:      12512 :         if (!v_proto.isUndefined()) {
     400         [ +  - ]:      12279 :             assert(v_proto.isObject() &&
     401                 :            :                    "Someone stored some weird value in a global slot");
     402                 :      12279 :             return &v_proto.toObject();
     403                 :            :         }
     404                 :            : 
     405                 :            :         // Workaround for bogus warning
     406                 :            :         // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=94554
     407                 :            :         // Note that the corresponding function pointers in the js::ClassSpec
     408                 :            :         // must be initialized as nullptr, not the default initializer! (see
     409                 :            :         // e.g. CairoPath::class_spec.finishInit)
     410                 :            :         using NullOpType =
     411                 :            :             std::integral_constant<js::ClassObjectCreationOp, nullptr>;
     412                 :            :         using CreateConstructorType =
     413                 :            :             std::integral_constant<js::ClassObjectCreationOp,
     414                 :            :                                    Base::klass.spec->createConstructor>;
     415                 :            :         using CreatePrototypeType =
     416                 :            :             std::integral_constant<js::ClassObjectCreationOp,
     417                 :            :                                    Base::klass.spec->createPrototype>;
     418                 :            :         using NullFuncsType =
     419                 :            :             std::integral_constant<const JSFunctionSpec*, nullptr>;
     420                 :            :         using ConstructorFuncsType =
     421                 :            :             std::integral_constant<const JSFunctionSpec*,
     422                 :            :                                    Base::klass.spec->constructorFunctions>;
     423                 :            :         using PrototypeFuncsType =
     424                 :            :             std::integral_constant<const JSFunctionSpec*,
     425                 :            :                                    Base::klass.spec->prototypeFunctions>;
     426                 :            :         using NullPropsType =
     427                 :            :             std::integral_constant<const JSPropertySpec*, nullptr>;
     428                 :            :         using ConstructorPropsType =
     429                 :            :             std::integral_constant<const JSPropertySpec*,
     430                 :            :                                    Base::klass.spec->constructorProperties>;
     431                 :            :         using PrototypePropsType =
     432                 :            :             std::integral_constant<const JSPropertySpec*,
     433                 :            :                                    Base::klass.spec->prototypeProperties>;
     434                 :            :         using NullFinishOpType =
     435                 :            :             std::integral_constant<js::FinishClassInitOp, nullptr>;
     436                 :            :         using FinishInitType =
     437                 :            :             std::integral_constant<js::FinishClassInitOp,
     438                 :            :                                    Base::klass.spec->finishInit>;
     439                 :            : 
     440                 :            :         // Create the prototype. If no createPrototype function is provided,
     441                 :            :         // then the default is to create a plain object as the prototype.
     442                 :        233 :         JS::RootedObject proto(cx);
     443                 :            :         if constexpr (!std::is_same_v<CreatePrototypeType, NullOpType>) {
     444                 :         80 :             proto = Base::klass.spec->createPrototype(cx, JSProto_Object);
     445                 :            :         } else {
     446                 :        153 :             proto = JS_NewPlainObject(cx);
     447                 :            :         }
     448         [ -  + ]:        233 :         if (!proto)
     449                 :          0 :             return nullptr;
     450                 :            : 
     451                 :            :         if constexpr (!std::is_same_v<PrototypePropsType, NullPropsType>) {
     452         [ -  + ]:        233 :             if (!JS_DefineProperties(cx, proto,
     453                 :        233 :                                      Base::klass.spec->prototypeProperties))
     454                 :          0 :                 return nullptr;
     455                 :            :         }
     456                 :            :         if constexpr (!std::is_same_v<PrototypeFuncsType, NullFuncsType>) {
     457         [ -  + ]:        165 :             if (!JS_DefineFunctions(cx, proto,
     458                 :        165 :                                     Base::klass.spec->prototypeFunctions))
     459                 :          0 :                 return nullptr;
     460                 :            :         }
     461                 :            : 
     462                 :        233 :         gjs_set_global_slot(global, Base::PROTOTYPE_SLOT,
     463                 :        233 :                             JS::ObjectValue(*proto));
     464                 :            : 
     465                 :            :         // Create the constructor. If no createConstructor function is provided,
     466                 :            :         // then the default is to call CWrapper::constructor() which calls
     467                 :            :         // Base::constructor_impl().
     468                 :        233 :         JS::RootedObject ctor_obj(cx);
     469                 :            :         if constexpr (!(Base::klass.spec->flags &
     470                 :            :                         js::ClassSpec::DontDefineConstructor)) {
     471                 :            :             if constexpr (!std::is_same_v<CreateConstructorType, NullOpType>) {
     472                 :         10 :                 ctor_obj =
     473                 :         10 :                     Base::klass.spec->createConstructor(cx, JSProto_Object);
     474                 :            :             } else {
     475                 :         18 :                 JSFunction* ctor = JS_NewFunction(
     476                 :            :                     cx, &Base::constructor, Base::constructor_nargs,
     477                 :         18 :                     JSFUN_CONSTRUCTOR, Base::klass.name);
     478                 :         18 :                 ctor_obj = JS_GetFunctionObject(ctor);
     479                 :            :             }
     480         [ +  - ]:         56 :             if (!ctor_obj ||
     481   [ -  +  -  + ]:         56 :                 !JS_LinkConstructorAndPrototype(cx, ctor_obj, proto))
     482                 :          0 :                 return nullptr;
     483                 :            :             if constexpr (!std::is_same_v<ConstructorPropsType,
     484                 :            :                                           NullPropsType>) {
     485                 :            :                 if (!JS_DefineProperties(
     486                 :            :                         cx, ctor_obj, Base::klass.spec->constructorProperties))
     487                 :            :                     return nullptr;
     488                 :            :             }
     489                 :            :             if constexpr (!std::is_same_v<ConstructorFuncsType,
     490                 :            :                                           NullFuncsType>) {
     491         [ -  + ]:          4 :                 if (!JS_DefineFunctions(cx, ctor_obj,
     492                 :          4 :                                         Base::klass.spec->constructorFunctions))
     493                 :          0 :                     return nullptr;
     494                 :            :             }
     495                 :            :         }
     496                 :            : 
     497                 :            :         if constexpr (!std::is_same_v<FinishInitType, NullFinishOpType>) {
     498         [ -  + ]:         26 :             if (!Base::klass.spec->finishInit(cx, ctor_obj, proto))
     499                 :          0 :                 return nullptr;
     500                 :            :         }
     501                 :            : 
     502                 :            :         // Put the constructor, if one exists, as a property on the module
     503                 :            :         // object. If module is not given, we are defining a global class.
     504         [ +  + ]:        233 :         if (ctor_obj) {
     505                 :         28 :             JS::RootedObject in_obj(cx, module);
     506         [ -  + ]:         28 :             if (!in_obj)
     507                 :          0 :                 in_obj = global;
     508                 :         28 :             JS::RootedId class_name(
     509                 :         28 :                 cx, gjs_intern_string_to_id(cx, Base::klass.name));
     510         [ +  - ]:         56 :             if (class_name.isVoid() ||
     511   [ -  +  -  + ]:         56 :                 !JS_DefinePropertyById(cx, in_obj, class_name, ctor_obj,
     512                 :            :                                        GJS_MODULE_PROP_FLAGS))
     513                 :          0 :                 return nullptr;
     514   [ +  -  +  - ]:         28 :         }
     515                 :            : 
     516                 :        233 :         gjs_debug(GJS_DEBUG_CONTEXT, "Initialized class %s prototype %p",
     517                 :        233 :                   Base::klass.name, proto.get());
     518                 :        233 :         return proto;
     519                 :      12512 :     }
     520                 :            : 
     521                 :            :     /*
     522                 :            :      * CWrapper::from_c_ptr():
     523                 :            :      *
     524                 :            :      * Create a new CWrapper JS object from the given C pointer. The pointer
     525                 :            :      * is copied using copy_ptr(), so you must implement that if you use this
     526                 :            :      * function.
     527                 :            :      */
     528                 :            :     GJS_JSAPI_RETURN_CONVENTION
     529                 :         20 :     static JSObject* from_c_ptr(JSContext* cx, Wrapped* ptr) {
     530                 :         20 :         JS::RootedObject proto(cx, Base::prototype(cx));
     531         [ -  + ]:         20 :         if (!proto)
     532                 :          0 :             return nullptr;
     533                 :            : 
     534                 :         20 :         JS::RootedObject wrapper(
     535                 :         20 :             cx, JS_NewObjectWithGivenProto(cx, &Base::klass, proto));
     536         [ -  + ]:         20 :         if (!wrapper)
     537                 :          0 :             return nullptr;
     538                 :            : 
     539                 :         20 :         CWrapperPointerOps<Base, Wrapped>::init_private(wrapper,
     540                 :            :                                                         Base::copy_ptr(ptr));
     541                 :            : 
     542                 :         20 :         debug_lifecycle(ptr, wrapper, "from_c_ptr");
     543                 :            : 
     544                 :         20 :         return wrapper;
     545                 :         20 :     }
     546                 :            : };

Generated by: LCOV version 1.14