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