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