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