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: 2008 litl, LLC
4 : :
5 : : #include <config.h>
6 : :
7 : : #include <stddef.h> // for size_t
8 : :
9 : : #include <girepository.h>
10 : : #include <glib.h>
11 : :
12 : : #include <js/CallArgs.h>
13 : : #include <js/Class.h>
14 : : #include <js/ErrorReport.h> // for JSEXN_TYPEERR
15 : : #include <js/Object.h> // for GetClass
16 : : #include <js/PropertyAndElement.h>
17 : : #include <js/PropertyDescriptor.h> // for JSPROP_READONLY
18 : : #include <js/PropertySpec.h> // for JSPropertySpec, JS_PS_END, JS_STR...
19 : : #include <js/RootingAPI.h>
20 : : #include <js/TypeDecls.h>
21 : : #include <js/Utility.h> // for UniqueChars
22 : : #include <js/Value.h>
23 : : #include <jsapi.h> // for JS_NewObjectForConstructor, JS_NewObjectWithG...
24 : :
25 : : #include "gi/cwrapper.h"
26 : : #include "gi/function.h"
27 : : #include "gi/info.h"
28 : : #include "gi/param.h"
29 : : #include "gi/repo.h"
30 : : #include "gi/wrapperutils.h"
31 : : #include "gjs/atoms.h"
32 : : #include "gjs/auto.h"
33 : : #include "gjs/context-private.h"
34 : : #include "gjs/jsapi-class.h"
35 : : #include "gjs/jsapi-util.h"
36 : : #include "gjs/macros.h"
37 : : #include "gjs/mem-private.h"
38 : : #include "util/log.h"
39 : :
40 : : extern struct JSClass gjs_param_class;
41 : :
42 : : // Reserved slots
43 : : static const size_t POINTER = 0;
44 : :
45 : : struct Param : Gjs::AutoParam {
46 : 298 : explicit Param(GParamSpec* param)
47 : 298 : : Gjs::AutoParam(param, Gjs::TakeOwnership{}) {}
48 : : };
49 : :
50 : 1907 : [[nodiscard]] static GParamSpec* param_value(JSContext* cx,
51 : : JS::HandleObject obj) {
52 [ - + ]: 1907 : if (!JS_InstanceOf(cx, obj, &gjs_param_class, nullptr))
53 : 0 : return nullptr;
54 : :
55 : 1907 : auto* priv = JS::GetMaybePtrFromReservedSlot<Param>(obj, POINTER);
56 [ + + ]: 1907 : return priv ? priv->get() : nullptr;
57 : : }
58 : :
59 : : /*
60 : : * The *resolved out parameter, on success, should be false to indicate that id
61 : : * was not resolved; and true if id was resolved.
62 : : */
63 : : GJS_JSAPI_RETURN_CONVENTION
64 : : static bool
65 : 1207 : param_resolve(JSContext *context,
66 : : JS::HandleObject obj,
67 : : JS::HandleId id,
68 : : bool *resolved)
69 : : {
70 [ + + ]: 1207 : if (!param_value(context, obj)) {
71 : : /* instance, not prototype */
72 : 684 : *resolved = false;
73 : 684 : return true;
74 : : }
75 : :
76 : 523 : JS::UniqueChars name;
77 [ - + ]: 523 : if (!gjs_get_string_id(context, id, &name))
78 : 0 : return false;
79 [ + + ]: 523 : if (!name) {
80 : 20 : *resolved = false;
81 : 20 : return true; /* not resolved, but no error */
82 : : }
83 : :
84 : 503 : GI::AutoObjectInfo info{g_irepository_find_by_gtype(nullptr, G_TYPE_PARAM)};
85 : : GI::AutoFunctionInfo method_info{
86 : 503 : g_object_info_find_method(info, name.get())};
87 : :
88 [ + + ]: 503 : if (!method_info) {
89 : 375 : *resolved = false;
90 : 375 : return true;
91 : : }
92 : : #if GJS_VERBOSE_ENABLE_GI_USAGE
93 : : _gjs_log_info_usage(method_info);
94 : : #endif
95 : :
96 [ + - ]: 128 : if (g_function_info_get_flags (method_info) & GI_FUNCTION_IS_METHOD) {
97 : 128 : gjs_debug(GJS_DEBUG_GOBJECT,
98 : : "Defining method %s in prototype for GObject.ParamSpec",
99 : : method_info.name());
100 : :
101 [ - + ]: 128 : if (!gjs_define_function(context, obj, G_TYPE_PARAM, method_info))
102 : 0 : return false;
103 : :
104 : 128 : *resolved = true; /* we defined the prop in obj */
105 : : }
106 : :
107 : 128 : return true;
108 : 523 : }
109 : :
110 : : GJS_JSAPI_RETURN_CONVENTION
111 : 0 : static bool gjs_param_constructor(JSContext* cx, unsigned argc, JS::Value* vp) {
112 : 0 : JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
113 : :
114 [ # # ]: 0 : if (!args.isConstructing()) {
115 : 0 : gjs_throw_constructor_error(cx);
116 : 0 : return false;
117 : : }
118 : :
119 : : JS::RootedObject new_object(
120 : 0 : cx, JS_NewObjectForConstructor(cx, &gjs_param_class, args));
121 [ # # ]: 0 : if (!new_object)
122 : 0 : return false;
123 : :
124 : 0 : GJS_INC_COUNTER(param);
125 : :
126 : 0 : args.rval().setObject(*new_object);
127 : 0 : return true;
128 : 0 : }
129 : :
130 : 355 : static void param_finalize(JS::GCContext*, JSObject* obj) {
131 : 355 : Param* priv = JS::GetMaybePtrFromReservedSlot<Param>(obj, POINTER);
132 : : gjs_debug_lifecycle(GJS_DEBUG_GPARAM, "finalize, obj %p priv %p", obj,
133 : : priv);
134 [ + + ]: 355 : if (!priv)
135 : 57 : return; /* wrong class? */
136 : :
137 : 298 : GJS_DEC_COUNTER(param);
138 : 298 : JS::SetReservedSlot(obj, POINTER, JS::UndefinedValue());
139 [ + - ]: 298 : delete priv;
140 : : }
141 : :
142 : : /* The bizarre thing about this vtable is that it applies to both
143 : : * instances of the object, and to the prototype that instances of the
144 : : * class have.
145 : : */
146 : : static const struct JSClassOps gjs_param_class_ops = {
147 : : nullptr, // addProperty
148 : : nullptr, // deleteProperty
149 : : nullptr, // enumerate
150 : : nullptr, // newEnumerate
151 : : param_resolve,
152 : : nullptr, // mayResolve
153 : : param_finalize};
154 : :
155 : : static JSPropertySpec proto_props[] = {
156 : : JS_STRING_SYM_PS(toStringTag, "GObject_ParamSpec", JSPROP_READONLY),
157 : : JS_PS_END};
158 : :
159 : : static constexpr js::ClassSpec class_spec = {
160 : : nullptr, // createConstructor
161 : : nullptr, // createPrototype
162 : : nullptr, // constructorFunctions
163 : : nullptr, // constructorProperties
164 : : nullptr, // prototypeFunctions
165 : : proto_props, // prototypeProperties
166 : : nullptr // finishInit
167 : : };
168 : :
169 : : struct JSClass gjs_param_class = {
170 : : "GObject_ParamSpec",
171 : : JSCLASS_HAS_RESERVED_SLOTS(1) | JSCLASS_BACKGROUND_FINALIZE,
172 : : &gjs_param_class_ops, &class_spec};
173 : :
174 : : GJS_JSAPI_RETURN_CONVENTION
175 : : static JSObject*
176 : 298 : gjs_lookup_param_prototype(JSContext *context)
177 : : {
178 : 298 : const GjsAtoms& atoms = GjsContextPrivate::atoms(context);
179 : : JS::RootedObject in_object(
180 : 298 : context, gjs_lookup_namespace_object_by_name(context, atoms.gobject()));
181 : :
182 [ - + ]: 298 : if (G_UNLIKELY (!in_object))
183 : 0 : return nullptr;
184 : :
185 : 298 : JS::RootedValue value(context);
186 [ + - - + ]: 596 : if (!JS_GetPropertyById(context, in_object, atoms.param_spec(), &value) ||
187 [ - + ]: 298 : G_UNLIKELY(!value.isObject()))
188 : 0 : return nullptr;
189 : :
190 : 298 : JS::RootedObject constructor(context, &value.toObject());
191 : 298 : g_assert(constructor);
192 : :
193 [ + - - + ]: 596 : if (!JS_GetPropertyById(context, constructor, atoms.prototype(), &value) ||
194 [ - + ]: 298 : G_UNLIKELY(!value.isObjectOrNull()))
195 : 0 : return nullptr;
196 : :
197 : 298 : return value.toObjectOrNull();
198 : 298 : }
199 : :
200 : : bool
201 : 58 : gjs_define_param_class(JSContext *context,
202 : : JS::HandleObject in_object)
203 : : {
204 : 58 : JS::RootedObject prototype(context), constructor(context);
205 [ - + ]: 58 : if (!gjs_init_class_dynamic(
206 : : context, in_object, nullptr, "GObject", "ParamSpec",
207 : : &gjs_param_class, gjs_param_constructor, 0,
208 : : proto_props, // props of prototype
209 : : nullptr, // funcs of prototype
210 : : nullptr, // props of constructor, MyConstructor.myprop
211 : : nullptr, // funcs of constructor
212 : : &prototype, &constructor))
213 : 0 : return false;
214 : :
215 [ - + ]: 58 : if (!gjs_wrapper_define_gtype_prop(context, constructor, G_TYPE_PARAM))
216 : 0 : return false;
217 : :
218 : 58 : GI::AutoObjectInfo info{g_irepository_find_by_gtype(nullptr, G_TYPE_PARAM)};
219 [ - + ]: 58 : if (!gjs_define_static_methods<InfoType::Object>(context, constructor,
220 : : G_TYPE_PARAM, info))
221 : 0 : return false;
222 : :
223 : 58 : gjs_debug(GJS_DEBUG_GPARAM,
224 : : "Defined class ParamSpec prototype is %p class %p in object %p",
225 : 58 : prototype.get(), &gjs_param_class, in_object.get());
226 : 58 : return true;
227 : 58 : }
228 : :
229 : : JSObject*
230 : 298 : gjs_param_from_g_param(JSContext *context,
231 : : GParamSpec *gparam)
232 : : {
233 : : JSObject *obj;
234 : :
235 [ - + ]: 298 : if (!gparam)
236 : 0 : return nullptr;
237 : :
238 : 596 : gjs_debug(GJS_DEBUG_GPARAM,
239 : : "Wrapping %s '%s' on %s with JSObject",
240 : 298 : g_type_name(G_TYPE_FROM_INSTANCE((GTypeInstance*) gparam)),
241 : : gparam->name,
242 : : g_type_name(gparam->owner_type));
243 : :
244 : 298 : JS::RootedObject proto(context, gjs_lookup_param_prototype(context));
245 : :
246 : 298 : obj = JS_NewObjectWithGivenProto(context, JS::GetClass(proto), proto);
247 : :
248 : 298 : GJS_INC_COUNTER(param);
249 : 298 : auto* priv = new Param(gparam);
250 : 298 : JS::SetReservedSlot(obj, POINTER, JS::PrivateValue(priv));
251 : :
252 : 298 : gjs_debug(GJS_DEBUG_GPARAM,
253 : : "JSObject created with param instance %p type %s", gparam,
254 : 298 : g_type_name(G_TYPE_FROM_INSTANCE(gparam)));
255 : :
256 : 298 : return obj;
257 : 298 : }
258 : :
259 : : GParamSpec*
260 : 350 : gjs_g_param_from_param(JSContext *context,
261 : : JS::HandleObject obj)
262 : : {
263 [ - + ]: 350 : if (!obj)
264 : 0 : return nullptr;
265 : :
266 : 350 : return param_value(context, obj);
267 : : }
268 : :
269 : : bool
270 : 350 : gjs_typecheck_param(JSContext *context,
271 : : JS::HandleObject object,
272 : : GType expected_type,
273 : : bool throw_error)
274 : : {
275 : : bool result;
276 : :
277 [ - + ]: 350 : if (!gjs_typecheck_instance(context, object, &gjs_param_class, throw_error))
278 : 0 : return false;
279 : :
280 : 350 : GParamSpec* param = param_value(context, object);
281 [ - + ]: 350 : if (!param) {
282 [ # # ]: 0 : if (throw_error) {
283 : 0 : gjs_throw_custom(context, JSEXN_TYPEERR, nullptr,
284 : : "Object is GObject.ParamSpec.prototype, not an "
285 : : "object instance - cannot convert to a GObject."
286 : : "ParamSpec instance");
287 : : }
288 : :
289 : 0 : return false;
290 : : }
291 : :
292 [ + + ]: 350 : if (expected_type != G_TYPE_NONE)
293 [ + - + - ]: 282 : result = g_type_is_a(G_TYPE_FROM_INSTANCE(param), expected_type);
294 : : else
295 : 68 : result = true;
296 : :
297 [ - + - - ]: 350 : if (!result && throw_error) {
298 : 0 : gjs_throw_custom(context, JSEXN_TYPEERR, nullptr,
299 : : "Object is of type %s - cannot convert to %s",
300 : 0 : g_type_name(G_TYPE_FROM_INSTANCE(param)),
301 : : g_type_name(expected_type));
302 : : }
303 : :
304 : 350 : return result;
305 : : }
|