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