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 <unordered_map>
8 : : #include <utility> // for move, pair
9 : :
10 : : #include <glib-object.h>
11 : : #include <glib.h>
12 : :
13 : : #include <js/CallAndConstruct.h>
14 : : #include <js/PropertyAndElement.h>
15 : : #include <js/PropertyDescriptor.h> // for JSPROP_READONLY
16 : : #include <js/Realm.h>
17 : : #include <js/RootingAPI.h>
18 : : #include <js/TypeDecls.h>
19 : : #include <js/Value.h>
20 : : #include <js/ValueArray.h>
21 : : #include <jsapi.h> // for JS_NewPlainObject
22 : : #include <mozilla/Maybe.h>
23 : :
24 : : #include "gi/gobject.h"
25 : : #include "gi/object.h"
26 : : #include "gi/value.h"
27 : : #include "gjs/auto.h"
28 : : #include "gjs/context-private.h"
29 : : #include "gjs/context.h"
30 : : #include "gjs/jsapi-util.h"
31 : : #include "gjs/macros.h"
32 : :
33 : : static std::unordered_map<GType, AutoParamArray> class_init_properties;
34 : :
35 : 1229 : [[nodiscard]] static JSContext* current_js_context() {
36 : 1229 : GjsContext* gjs = gjs_context_get_current();
37 : 1229 : return static_cast<JSContext*>(gjs_context_get_native_context(gjs));
38 : : }
39 : :
40 : 125 : void push_class_init_properties(GType gtype, AutoParamArray* params) {
41 : 125 : class_init_properties[gtype] = std::move(*params);
42 : 125 : }
43 : :
44 : 125 : bool pop_class_init_properties(GType gtype, AutoParamArray* params_out) {
45 : 125 : auto found = class_init_properties.find(gtype);
46 [ - + ]: 125 : if (found == class_init_properties.end())
47 : 0 : return false;
48 : :
49 : 125 : *params_out = std::move(found->second);
50 : 125 : class_init_properties.erase(found);
51 : 125 : return true;
52 : : }
53 : :
54 : : GJS_JSAPI_RETURN_CONVENTION
55 : 217 : static bool jsobj_set_gproperty(JSContext* cx, JS::HandleObject object,
56 : : const GValue* value, GParamSpec* pspec) {
57 : 217 : JS::RootedValue jsvalue(cx);
58 [ - + ]: 217 : if (!gjs_value_from_g_value(cx, &jsvalue, value))
59 : 0 : return false;
60 : :
61 : 217 : Gjs::AutoChar underscore_name{gjs_hyphen_to_underscore(pspec->name)};
62 : :
63 [ + + ]: 217 : if (pspec->flags & G_PARAM_CONSTRUCT_ONLY) {
64 : 162 : unsigned flags = GJS_MODULE_PROP_FLAGS | JSPROP_READONLY;
65 : 162 : Gjs::AutoChar camel_name{gjs_hyphen_to_camel(pspec->name)};
66 : :
67 [ + - ]: 162 : if (g_param_spec_get_qdata(pspec, ObjectBase::custom_property_quark())) {
68 : 162 : JS::Rooted<mozilla::Maybe<JS::PropertyDescriptor>> jsprop(cx);
69 : 162 : JS::RootedObject holder(cx);
70 : 162 : JS::RootedObject getter(cx);
71 : :
72 : : // Ensure to call any associated setter method
73 [ + + ]: 162 : if (!g_str_equal(underscore_name.get(), pspec->name)) {
74 [ - + ]: 42 : if (!JS_GetPropertyDescriptor(cx, object, underscore_name,
75 : : &jsprop, &holder)) {
76 : 0 : return false;
77 : : }
78 : :
79 [ + + - + ]: 42 : if (jsprop.isSome() && jsprop->setter() &&
80 [ - - - + ]: 42 : !JS_SetProperty(cx, object, underscore_name, jsvalue)) {
81 : 0 : return false;
82 : : }
83 [ + + + - : 42 : if (jsprop.isSome() && jsprop->getter())
+ + ]
84 : 14 : getter.set(jsprop->getter());
85 : : }
86 : :
87 [ + + ]: 162 : if (!g_str_equal(camel_name.get(), pspec->name)) {
88 [ - + ]: 42 : if (!JS_GetPropertyDescriptor(cx, object, camel_name, &jsprop,
89 : : &holder)) {
90 : 0 : return false;
91 : : }
92 : :
93 [ + + + - ]: 56 : if (jsprop.isSome() && jsprop.value().setter() &&
94 [ - + - + ]: 56 : !JS_SetProperty(cx, object, camel_name, jsvalue)) {
95 : 0 : return false;
96 : : }
97 [ + + - + : 42 : if (!getter && jsprop.isSome() && jsprop->getter())
- - - + ]
98 : 0 : getter.set(jsprop->getter());
99 : : }
100 : :
101 [ - + ]: 162 : if (!JS_GetPropertyDescriptor(cx, object, pspec->name, &jsprop,
102 : : &holder))
103 : 0 : return false;
104 [ + + + + ]: 251 : if (jsprop.isSome() && jsprop.value().setter() &&
105 [ - + - + ]: 251 : !JS_SetProperty(cx, object, pspec->name, jsvalue))
106 : 0 : return false;
107 [ + + + + : 162 : if (!getter && jsprop.isSome() && jsprop->getter())
+ - + + ]
108 : 110 : getter.set(jsprop->getter());
109 : :
110 : : // If a getter is found, redefine the property with that getter
111 : : // and no setter.
112 [ + + ]: 162 : if (getter)
113 : 124 : return JS_DefineProperty(cx, object, underscore_name, getter,
114 [ + - ]: 124 : nullptr, GJS_MODULE_PROP_FLAGS) &&
115 : 124 : JS_DefineProperty(cx, object, camel_name, getter,
116 [ + - + - ]: 248 : nullptr, GJS_MODULE_PROP_FLAGS) &&
117 : 124 : JS_DefineProperty(cx, object, pspec->name, getter,
118 : 124 : nullptr, GJS_MODULE_PROP_FLAGS);
119 [ + + + + : 410 : }
+ + ]
120 : :
121 [ + - ]: 76 : return JS_DefineProperty(cx, object, underscore_name, jsvalue, flags) &&
122 [ + - + - ]: 76 : JS_DefineProperty(cx, object, camel_name, jsvalue, flags) &&
123 : 76 : JS_DefineProperty(cx, object, pspec->name, jsvalue, flags);
124 : 162 : }
125 : :
126 : 55 : return JS_SetProperty(cx, object, underscore_name, jsvalue);
127 : 217 : }
128 : :
129 : 135 : static void gjs_object_base_init(void* klass) {
130 : 135 : auto* priv = ObjectPrototype::for_gtype(G_OBJECT_CLASS_TYPE(klass));
131 [ - + ]: 135 : if (priv)
132 : 0 : priv->ref_vfuncs();
133 : 135 : }
134 : :
135 : 0 : static void gjs_object_base_finalize(void* klass) {
136 : 0 : auto* priv = ObjectPrototype::for_gtype(G_OBJECT_CLASS_TYPE(klass));
137 [ # # ]: 0 : if (priv)
138 : 0 : priv->unref_vfuncs();
139 : 0 : }
140 : :
141 : 477 : static GObject* gjs_object_constructor(
142 : : GType type, unsigned n_construct_properties,
143 : : GObjectConstructParam* construct_properties) {
144 : 477 : JSContext* cx = current_js_context();
145 : 477 : GjsContextPrivate* gjs = GjsContextPrivate::from_cx(cx);
146 : :
147 [ + + ]: 477 : if (!gjs->object_init_list().empty()) {
148 : 473 : GType parent_type = g_type_parent(type);
149 : :
150 : : /* The object is being constructed from JS:
151 : : * Simply chain up to the first non-gjs constructor
152 : : */
153 [ + + ]: 531 : while (G_OBJECT_CLASS(g_type_class_peek(parent_type))->constructor ==
154 : : gjs_object_constructor)
155 : 58 : parent_type = g_type_parent(parent_type);
156 : :
157 : 473 : return G_OBJECT_CLASS(g_type_class_peek(parent_type))
158 : 473 : ->constructor(type, n_construct_properties, construct_properties);
159 : : }
160 : :
161 : : /* The object is being constructed from native code (e.g. GtkBuilder):
162 : : * Construct the JS object from the constructor, then use the GObject
163 : : * that was associated in gjs_object_custom_init()
164 : : */
165 : 4 : Gjs::AutoMainRealm ar{gjs};
166 : :
167 : 4 : JS::RootedValue constructor{cx};
168 [ - + ]: 4 : if (!gjs_lookup_object_constructor(cx, type, &constructor))
169 : 0 : return nullptr;
170 : :
171 : 4 : JS::RootedObject object(cx);
172 [ + + ]: 4 : if (n_construct_properties) {
173 : 3 : JS::RootedObject props_hash(cx, JS_NewPlainObject(cx));
174 : :
175 [ + + ]: 6 : for (unsigned i = 0; i < n_construct_properties; i++)
176 [ - + ]: 3 : if (!jsobj_set_gproperty(cx, props_hash,
177 : 3 : construct_properties[i].value,
178 : 3 : construct_properties[i].pspec))
179 : 0 : return nullptr;
180 : :
181 : 3 : JS::RootedValueArray<1> args(cx);
182 : 3 : args[0].set(JS::ObjectValue(*props_hash));
183 : :
184 [ - + ]: 3 : if (!JS::Construct(cx, constructor, args, &object))
185 : 0 : return nullptr;
186 [ + - + - : 4 : } else if (!JS::Construct(cx, constructor, JS::HandleValueArray::empty(),
- + ]
187 : : &object)) {
188 : 0 : return nullptr;
189 : : }
190 : :
191 : 4 : auto* priv = ObjectBase::for_js_nocheck(object);
192 : : /* Should have been set in init_impl() and pushed into object_init_list,
193 : : * then popped from object_init_list in gjs_object_custom_init() */
194 : 4 : g_assert(priv);
195 : : /* We only hold a toggle ref at this point, add back a ref that the
196 : : * native code can own.
197 : : */
198 : 4 : return G_OBJECT(g_object_ref(priv->to_instance()->ptr()));
199 : 4 : }
200 : :
201 : 215 : static void gjs_object_set_gproperty(GObject* object,
202 : : unsigned property_id [[maybe_unused]],
203 : : const GValue* value, GParamSpec* pspec) {
204 : 215 : auto* priv = ObjectInstance::for_gobject(object);
205 [ + + ]: 215 : if (!priv) {
206 : 1 : g_warning("Wrapper for GObject %p was disposed, cannot set property %s",
207 : : object, g_param_spec_get_name(pspec));
208 : 1 : return;
209 : : }
210 : :
211 : 214 : JSContext* cx = current_js_context();
212 : :
213 : 214 : JS::RootedObject js_obj(cx, priv->wrapper());
214 : 214 : JSAutoRealm ar(cx, js_obj);
215 : :
216 [ + + ]: 214 : if (!jsobj_set_gproperty(cx, js_obj, value, pspec))
217 : 2 : gjs_log_exception_uncaught(cx);
218 : 214 : }
219 : :
220 : 7 : static void gjs_object_get_gproperty(GObject* object,
221 : : unsigned property_id [[maybe_unused]],
222 : : GValue* value, GParamSpec* pspec) {
223 : 7 : auto* priv = ObjectInstance::for_gobject(object);
224 [ - + ]: 7 : if (!priv) {
225 : 0 : g_warning("Wrapper for GObject %p was disposed, cannot get property %s",
226 : : object, g_param_spec_get_name(pspec));
227 : 1 : return;
228 : : }
229 : :
230 : 7 : JSContext* cx = current_js_context();
231 : :
232 : 7 : JS::RootedObject js_obj(cx, priv->wrapper());
233 : 7 : JS::RootedValue jsvalue(cx);
234 : 7 : JSAutoRealm ar(cx, js_obj);
235 : :
236 : 7 : Gjs::AutoChar underscore_name{gjs_hyphen_to_underscore(pspec->name)};
237 [ + + ]: 7 : if (!JS_GetProperty(cx, js_obj, underscore_name, &jsvalue)) {
238 : 1 : gjs_log_exception_uncaught(cx);
239 : 1 : return;
240 : : }
241 [ - + ]: 6 : if (!gjs_value_to_g_value(cx, jsvalue, value))
242 : 0 : gjs_log_exception(cx);
243 [ + + + + : 10 : }
+ + + + ]
244 : :
245 : 116 : static void gjs_object_class_init(void* class_pointer, void*) {
246 : 116 : GObjectClass* klass = G_OBJECT_CLASS(class_pointer);
247 : 116 : GType gtype = G_OBJECT_CLASS_TYPE(klass);
248 : :
249 : 116 : klass->constructor = gjs_object_constructor;
250 : 116 : klass->set_property = gjs_object_set_gproperty;
251 : 116 : klass->get_property = gjs_object_get_gproperty;
252 : :
253 : 116 : AutoParamArray properties;
254 [ - + ]: 116 : if (!pop_class_init_properties(gtype, &properties))
255 : 0 : return;
256 : :
257 : 116 : unsigned i = 0;
258 [ + + ]: 181 : for (Gjs::AutoParam& pspec : properties) {
259 : 65 : g_param_spec_set_qdata(pspec, ObjectBase::custom_property_quark(),
260 : : GINT_TO_POINTER(1));
261 : 65 : g_object_class_install_property(klass, ++i, pspec);
262 : : }
263 [ + - ]: 116 : }
264 : :
265 : 531 : static void gjs_object_custom_init(GTypeInstance* instance,
266 : : void* g_class [[maybe_unused]]) {
267 : 531 : JSContext* cx = current_js_context();
268 : 531 : GjsContextPrivate* gjs = GjsContextPrivate::from_cx(cx);
269 : :
270 [ - + ]: 531 : if (gjs->object_init_list().empty())
271 : 58 : return;
272 : :
273 : 531 : JS::RootedObject object(cx, gjs->object_init_list().back());
274 : 531 : auto* priv_base = ObjectBase::for_js_nocheck(object);
275 : 531 : g_assert(priv_base); // Should have been set in init_impl()
276 : 531 : ObjectInstance* priv = priv_base->to_instance();
277 : :
278 [ + + ]: 531 : if (priv_base->gtype() != G_TYPE_FROM_INSTANCE(instance)) {
279 : : /* This is not the most derived instance_init function,
280 : : do nothing.
281 : : */
282 : 58 : return;
283 : : }
284 : :
285 : 473 : gjs->object_init_list().popBack();
286 : :
287 [ - + ]: 473 : if (!priv->init_custom_class_from_gobject(cx, object, G_OBJECT(instance)))
288 : 0 : gjs_log_exception_uncaught(cx);
289 [ + + ]: 531 : }
290 : :
291 : 9 : static void gjs_interface_init(void* g_iface, void*) {
292 : 9 : GType gtype = G_TYPE_FROM_INTERFACE(g_iface);
293 : :
294 : 9 : AutoParamArray properties;
295 [ - + ]: 9 : if (!pop_class_init_properties(gtype, &properties))
296 : 0 : return;
297 : :
298 [ + + ]: 11 : for (Gjs::AutoParam& pspec : properties) {
299 : 2 : g_param_spec_set_qdata(pspec, ObjectBase::custom_property_quark(),
300 : : GINT_TO_POINTER(1));
301 : 2 : g_object_interface_install_property(g_iface, pspec);
302 : : }
303 [ + - ]: 9 : }
304 : :
305 : : constexpr GTypeInfo gjs_gobject_class_info = {
306 : : 0, // class_size
307 : :
308 : : gjs_object_base_init,
309 : : gjs_object_base_finalize,
310 : :
311 : : gjs_object_class_init,
312 : : GClassFinalizeFunc(nullptr),
313 : : nullptr, // class_data
314 : :
315 : : 0, // instance_size
316 : : 0, // n_preallocs
317 : : gjs_object_custom_init,
318 : : };
319 : :
320 : : constexpr GTypeInfo gjs_gobject_interface_info = {
321 : : sizeof(GTypeInterface), // class_size
322 : :
323 : : GBaseInitFunc(nullptr),
324 : : GBaseFinalizeFunc(nullptr),
325 : :
326 : : gjs_interface_init,
327 : : GClassFinalizeFunc(nullptr),
328 : : nullptr, // class_data
329 : :
330 : : 0, // instance_size
331 : : 0, // n_preallocs
332 : : nullptr, // instance_init
333 : : };
|