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 : : };
|