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 : : #ifndef GI_OBJECT_H_
6 : : #define GI_OBJECT_H_
7 : :
8 : : #include <config.h>
9 : :
10 : : #include <stddef.h> // for size_t
11 : : #include <stdint.h> // for uint32_t
12 : :
13 : : #include <functional>
14 : : #include <vector>
15 : :
16 : : #include <girepository.h>
17 : : #include <glib-object.h>
18 : : #include <glib.h>
19 : :
20 : : #include <js/AllocPolicy.h>
21 : : #include <js/GCHashTable.h> // for GCHashMap
22 : : #include <js/HashTable.h> // for DefaultHasher
23 : : #include <js/Id.h>
24 : : #include <js/PropertySpec.h>
25 : : #include <js/RootingAPI.h>
26 : : #include <js/TypeDecls.h>
27 : : #include <mozilla/HashFunctions.h> // for HashGeneric, HashNumber
28 : : #include <mozilla/Likely.h> // for MOZ_LIKELY
29 : :
30 : : #include "gi/value.h"
31 : : #include "gi/wrapperutils.h"
32 : : #include "gjs/jsapi-util-root.h"
33 : : #include "gjs/jsapi-util.h"
34 : : #include "gjs/macros.h"
35 : : #include "util/log.h"
36 : :
37 : : class GjsAtoms;
38 : : class JSTracer;
39 : : namespace JS {
40 : : class CallArgs;
41 : : }
42 : : namespace Gjs {
43 : : namespace Test {
44 : : struct ObjectInstance;
45 : : }
46 : : }
47 : : class ObjectInstance;
48 : : class ObjectPrototype;
49 : :
50 : : /*
51 : : * ObjectBase:
52 : : *
53 : : * Specialization of GIWrapperBase for GObject instances. See the documentation
54 : : * in wrapperutils.h.
55 : : *
56 : : * It's important that ObjectBase and ObjectInstance not grow in size without a
57 : : * very good reason. There can be tens, maybe hundreds of thousands of these
58 : : * objects alive in a typical gnome-shell run, so even 8 more bytes will add up.
59 : : * It's less critical that ObjectPrototype stay small, since only one of these
60 : : * is allocated per GType.
61 : : */
62 : : class ObjectBase
63 : : : public GIWrapperBase<ObjectBase, ObjectPrototype, ObjectInstance> {
64 : : friend class GIWrapperBase<ObjectBase, ObjectPrototype, ObjectInstance>;
65 : :
66 : : protected:
67 : 2158 : explicit ObjectBase(ObjectPrototype* proto = nullptr)
68 : 2158 : : GIWrapperBase(proto) {}
69 : :
70 : : public:
71 : : using SignalMatchFunc = guint(gpointer, GSignalMatchType, guint, GQuark,
72 : : GClosure*, gpointer, gpointer);
73 : : static constexpr GjsDebugTopic DEBUG_TOPIC = GJS_DEBUG_GOBJECT;
74 : : static constexpr const char* DEBUG_TAG = "GObject";
75 : :
76 : : static const struct JSClassOps class_ops;
77 : : static const struct JSClass klass;
78 : : static JSFunctionSpec proto_methods[];
79 : : static JSPropertySpec proto_properties[];
80 : :
81 : : static GObject* to_c_ptr(JSContext* cx, JS::HandleObject obj) = delete;
82 : : GJS_JSAPI_RETURN_CONVENTION
83 : : static bool to_c_ptr(JSContext* cx, JS::HandleObject obj, GObject** ptr);
84 : : GJS_JSAPI_RETURN_CONVENTION
85 : : static bool transfer_to_gi_argument(JSContext* cx, JS::HandleObject obj,
86 : : GIArgument* arg,
87 : : GIDirection transfer_direction,
88 : : GITransfer transfer_ownership,
89 : : GType expected_gtype,
90 : : GIBaseInfo* expected_info = nullptr);
91 : :
92 : : private:
93 : : // This is used in debug methods only.
94 : : [[nodiscard]] const void* jsobj_addr() const;
95 : :
96 : : /* Helper methods */
97 : :
98 : : protected:
99 : 5651 : void debug_lifecycle(const char* message) const {
100 : 5651 : GIWrapperBase::debug_lifecycle(jsobj_addr(), message);
101 : 5651 : }
102 : :
103 : : [[nodiscard]] bool id_is_never_lazy(jsid name, const GjsAtoms& atoms);
104 : : [[nodiscard]] bool is_custom_js_class();
105 : :
106 : : public:
107 : : void type_query_dynamic_safe(GTypeQuery* query);
108 : :
109 : : GJS_JSAPI_RETURN_CONVENTION
110 : : static bool typecheck(JSContext* cx, JS::HandleObject obj,
111 : : GIObjectInfo* expected_info, GType expected_gtype);
112 : 17 : [[nodiscard]] static bool typecheck(JSContext* cx, JS::HandleObject obj,
113 : : GIObjectInfo* expected_info,
114 : : GType expected_gtype,
115 : : GjsTypecheckNoThrow no_throw) {
116 : 17 : return GIWrapperBase::typecheck(cx, obj, expected_info, expected_gtype,
117 : 17 : no_throw);
118 : : }
119 : :
120 : : /* JSClass operations */
121 : :
122 : : static bool add_property(JSContext* cx, JS::HandleObject obj,
123 : : JS::HandleId id, JS::HandleValue value);
124 : :
125 : : /* JS property getters/setters */
126 : :
127 : : public:
128 : : GJS_JSAPI_RETURN_CONVENTION
129 : : static bool prop_getter(JSContext* cx, unsigned argc, JS::Value* vp);
130 : : GJS_JSAPI_RETURN_CONVENTION
131 : : static bool field_getter(JSContext* cx, unsigned argc, JS::Value* vp);
132 : : GJS_JSAPI_RETURN_CONVENTION
133 : : static bool prop_setter(JSContext* cx, unsigned argc, JS::Value* vp);
134 : : GJS_JSAPI_RETURN_CONVENTION
135 : : static bool field_setter(JSContext* cx, unsigned argc, JS::Value* vp);
136 : :
137 : : /* JS methods */
138 : :
139 : : GJS_JSAPI_RETURN_CONVENTION
140 : : static bool connect(JSContext* cx, unsigned argc, JS::Value* vp);
141 : : GJS_JSAPI_RETURN_CONVENTION
142 : : static bool connect_after(JSContext* cx, unsigned argc, JS::Value* vp);
143 : : GJS_JSAPI_RETURN_CONVENTION
144 : : static bool emit(JSContext* cx, unsigned argc, JS::Value* vp);
145 : : GJS_JSAPI_RETURN_CONVENTION
146 : : static bool signal_find(JSContext* cx, unsigned argc, JS::Value* vp);
147 : : template <SignalMatchFunc(*MATCH_FUNC)>
148 : : GJS_JSAPI_RETURN_CONVENTION static bool signals_action(JSContext* cx,
149 : : unsigned argc,
150 : : JS::Value* vp);
151 : : GJS_JSAPI_RETURN_CONVENTION
152 : : static bool to_string(JSContext* cx, unsigned argc, JS::Value* vp);
153 : : GJS_JSAPI_RETURN_CONVENTION
154 : : static bool init_gobject(JSContext* cx, unsigned argc, JS::Value* vp);
155 : : GJS_JSAPI_RETURN_CONVENTION
156 : : static bool hook_up_vfunc(JSContext* cx, unsigned argc, JS::Value* vp);
157 : :
158 : : /* Quarks */
159 : :
160 : : protected:
161 : : [[nodiscard]] static GQuark instance_strings_quark();
162 : :
163 : : public:
164 : : [[nodiscard]] static GQuark custom_type_quark();
165 : : [[nodiscard]] static GQuark custom_property_quark();
166 : : [[nodiscard]] static GQuark disposed_quark();
167 : : };
168 : :
169 : : // See https://bugzilla.mozilla.org/show_bug.cgi?id=1614220
170 : : struct IdHasher {
171 : : typedef jsid Lookup;
172 : 12717 : static mozilla::HashNumber hash(jsid id) {
173 [ + + ]: 12717 : if (MOZ_LIKELY(id.isString()))
174 : 12224 : return js::DefaultHasher<JSString*>::hash(id.toString());
175 [ + - ]: 493 : if (id.isSymbol())
176 : 493 : return js::DefaultHasher<JS::Symbol*>::hash(id.toSymbol());
177 : 0 : return mozilla::HashGeneric(id.asRawBits());
178 : : }
179 : 6221 : static bool match(jsid id1, jsid id2) { return id1 == id2; }
180 : : };
181 : :
182 : : class ObjectPrototype
183 : : : public GIWrapperPrototype<ObjectBase, ObjectPrototype, ObjectInstance> {
184 : : friend class GIWrapperPrototype<ObjectBase, ObjectPrototype,
185 : : ObjectInstance>;
186 : : friend class GIWrapperBase<ObjectBase, ObjectPrototype, ObjectInstance>;
187 : :
188 : : using PropertyCache =
189 : : JS::GCHashMap<JS::Heap<JSString*>, GjsAutoParam,
190 : : js::DefaultHasher<JSString*>, js::SystemAllocPolicy>;
191 : : using FieldCache =
192 : : JS::GCHashMap<JS::Heap<JSString*>, GjsAutoInfo<GI_INFO_TYPE_FIELD>,
193 : : js::DefaultHasher<JSString*>, js::SystemAllocPolicy>;
194 : : using NegativeLookupCache =
195 : : JS::GCHashSet<JS::Heap<jsid>, IdHasher, js::SystemAllocPolicy>;
196 : :
197 : : PropertyCache m_property_cache;
198 : : FieldCache m_field_cache;
199 : : NegativeLookupCache m_unresolvable_cache;
200 : : // a list of vfunc GClosures installed on this prototype, used when tracing
201 : : std::vector<GClosure*> m_vfuncs;
202 : : // a list of interface types explicitly associated with this prototype,
203 : : // by gjs_add_interface
204 : : std::vector<GType> m_interface_gtypes;
205 : :
206 : : ObjectPrototype(GIObjectInfo* info, GType gtype);
207 : : ~ObjectPrototype();
208 : :
209 : : static constexpr InfoType::Tag info_type_tag = InfoType::Object;
210 : :
211 : : public:
212 : : [[nodiscard]] static ObjectPrototype* for_gtype(GType gtype);
213 : :
214 : : /* Helper methods */
215 : : private:
216 : : GJS_JSAPI_RETURN_CONVENTION
217 : : bool get_parent_proto(JSContext* cx, JS::MutableHandleObject proto) const;
218 : : GJS_JSAPI_RETURN_CONVENTION
219 : : bool get_parent_constructor(JSContext* cx,
220 : : JS::MutableHandleObject constructor) const;
221 : :
222 : : [[nodiscard]] bool is_vfunc_unchanged(GIVFuncInfo* info);
223 : : static void vfunc_invalidated_notify(void* data, GClosure* closure);
224 : :
225 : : GJS_JSAPI_RETURN_CONVENTION
226 : : bool lazy_define_gobject_property(JSContext* cx, JS::HandleObject obj,
227 : : JS::HandleId id, bool* resolved,
228 : : const char* name);
229 : :
230 : : enum ResolveWhat { ConsiderOnlyMethods, ConsiderMethodsAndProperties };
231 : : GJS_JSAPI_RETURN_CONVENTION
232 : : bool resolve_no_info(JSContext* cx, JS::HandleObject obj, JS::HandleId id,
233 : : bool* resolved, const char* name,
234 : : ResolveWhat resolve_props);
235 : : GJS_JSAPI_RETURN_CONVENTION
236 : : bool uncached_resolve(JSContext* cx, JS::HandleObject obj, JS::HandleId id,
237 : : const char* name, bool* resolved);
238 : :
239 : : public:
240 : : void set_interfaces(GType* interface_gtypes, uint32_t n_interface_gtypes);
241 : : void set_type_qdata(void);
242 : : GJS_JSAPI_RETURN_CONVENTION
243 : : GParamSpec* find_param_spec_from_id(JSContext* cx, JS::HandleString key);
244 : : GJS_JSAPI_RETURN_CONVENTION
245 : : GIFieldInfo* lookup_cached_field_info(JSContext* cx, JS::HandleString key);
246 : : GJS_JSAPI_RETURN_CONVENTION
247 : : bool props_to_g_parameters(JSContext* cx, JS::HandleObject props,
248 : : std::vector<const char*>* names,
249 : : AutoGValueVector* values);
250 : :
251 : : GJS_JSAPI_RETURN_CONVENTION
252 : : static bool define_class(JSContext* cx, JS::HandleObject in_object,
253 : : GIObjectInfo* info, GType gtype,
254 : : GType* interface_gtypes,
255 : : uint32_t n_interface_gtypes,
256 : : JS::MutableHandleObject constructor,
257 : : JS::MutableHandleObject prototype);
258 : :
259 : 0 : void ref_vfuncs(void) {
260 [ # # ]: 0 : for (GClosure* closure : m_vfuncs)
261 : 0 : g_closure_ref(closure);
262 : 0 : }
263 : 0 : void unref_vfuncs(void) {
264 [ # # ]: 0 : for (GClosure* closure : m_vfuncs)
265 : 0 : g_closure_unref(closure);
266 : 0 : }
267 : :
268 : : /* JSClass operations */
269 : : private:
270 : : GJS_JSAPI_RETURN_CONVENTION
271 : : bool resolve_impl(JSContext* cx, JS::HandleObject obj, JS::HandleId id,
272 : : bool* resolved);
273 : :
274 : : GJS_JSAPI_RETURN_CONVENTION
275 : : bool new_enumerate_impl(JSContext* cx, JS::HandleObject obj,
276 : : JS::MutableHandleIdVector properties,
277 : : bool only_enumerable);
278 : : void trace_impl(JSTracer* tracer);
279 : :
280 : : /* JS methods */
281 : : public:
282 : : GJS_JSAPI_RETURN_CONVENTION
283 : : bool hook_up_vfunc_impl(JSContext* cx, const JS::CallArgs& args);
284 : : };
285 : :
286 : : class ObjectInstance : public GIWrapperInstance<ObjectBase, ObjectPrototype,
287 : : ObjectInstance, GObject> {
288 : : friend class GIWrapperInstance<ObjectBase, ObjectPrototype, ObjectInstance,
289 : : GObject>;
290 : : friend class GIWrapperBase<ObjectBase, ObjectPrototype, ObjectInstance>;
291 : : friend class ObjectBase; // for add_property, prop_getter, etc.
292 : : friend struct Gjs::Test::ObjectInstance;
293 : :
294 : : // GIWrapperInstance::m_ptr may be null in ObjectInstance.
295 : :
296 : : GjsMaybeOwned<JSObject*> m_wrapper;
297 : : // a list of all GClosures installed on this object (from signal connections
298 : : // and scope-notify callbacks passed to methods), used when tracing
299 : : std::vector<GClosure*> m_closures;
300 : :
301 : : bool m_wrapper_finalized : 1;
302 : : bool m_gobj_disposed : 1;
303 : : bool m_gobj_finalized : 1;
304 : :
305 : : /* True if this object has visible JS state, and thus its lifecycle is
306 : : * managed using toggle references. False if this object just keeps a
307 : : * hard ref on the underlying GObject, and may be finalized at will. */
308 : : bool m_uses_toggle_ref : 1;
309 : :
310 : : static bool s_weak_pointer_callback;
311 : :
312 : : /* Constructors */
313 : :
314 : : private:
315 : : ObjectInstance(ObjectPrototype* prototype, JS::HandleObject obj);
316 : : ~ObjectInstance();
317 : :
318 : : GJS_JSAPI_RETURN_CONVENTION
319 : : static ObjectInstance* new_for_gobject(JSContext* cx, GObject* gobj);
320 : :
321 : : // Extra method to get an existing ObjectInstance from qdata
322 : :
323 : : public:
324 : : [[nodiscard]] static ObjectInstance* for_gobject(GObject* gobj);
325 : :
326 : : /* Accessors */
327 : :
328 : : private:
329 : 2485 : [[nodiscard]] bool has_wrapper() const { return !!m_wrapper; }
330 : :
331 : : public:
332 : 2365 : [[nodiscard]] JSObject* wrapper() const { return m_wrapper; }
333 : :
334 : : /* Methods to manipulate the JS object wrapper */
335 : :
336 : : private:
337 : 1825 : void discard_wrapper(void) { m_wrapper.reset(); }
338 : 1118 : void switch_to_rooted(JSContext* cx) { m_wrapper.switch_to_rooted(cx); }
339 : 938 : void switch_to_unrooted(JSContext* cx) { m_wrapper.switch_to_unrooted(cx); }
340 : 1247 : [[nodiscard]] bool update_after_gc(JSTracer* trc) {
341 : 1247 : return m_wrapper.update_after_gc(trc);
342 : : }
343 : 7701 : [[nodiscard]] bool wrapper_is_rooted() const { return m_wrapper.rooted(); }
344 : : void release_native_object(void);
345 : : void associate_js_gobject(JSContext* cx, JS::HandleObject obj,
346 : : GObject* gobj);
347 : : void disassociate_js_gobject(void);
348 : : void handle_context_dispose(void);
349 : : [[nodiscard]] bool weak_pointer_was_finalized(JSTracer* trc);
350 : : static void ensure_weak_pointer_callback(JSContext* cx);
351 : : static void update_heap_wrapper_weak_pointers(JSTracer* trc,
352 : : JS::Compartment*, void* data);
353 : :
354 : : public:
355 : : void toggle_down(void);
356 : : void toggle_up(void);
357 : :
358 : : GJS_JSAPI_RETURN_CONVENTION
359 : : static JSObject* wrapper_from_gobject(JSContext* cx, GObject* ptr);
360 : :
361 : : GJS_JSAPI_RETURN_CONVENTION
362 : : static bool set_value_from_gobject(JSContext* cx, GObject*,
363 : : JS::MutableHandleValue);
364 : :
365 : : /* Methods to manipulate the list of closures */
366 : :
367 : : private:
368 : : void invalidate_closures();
369 : : static void closure_invalidated_notify(void* data, GClosure* closure);
370 : :
371 : : public:
372 : : GJS_JSAPI_RETURN_CONVENTION bool associate_closure(JSContext*, GClosure*);
373 : :
374 : : /* Helper methods */
375 : :
376 : : private:
377 : : void set_object_qdata(void);
378 : : void unset_object_qdata(void);
379 : : void track_gobject_finalization();
380 : : void ignore_gobject_finalization();
381 : : void check_js_object_finalized(void);
382 : : GJS_JSAPI_RETURN_CONVENTION bool ensure_uses_toggle_ref(JSContext* cx);
383 : : [[nodiscard]] bool check_gobject_disposed_or_finalized(
384 : : const char* for_what) const;
385 : : [[nodiscard]] bool check_gobject_finalized(const char* for_what) const;
386 : : GJS_JSAPI_RETURN_CONVENTION
387 : : bool signal_match_arguments_from_object(
388 : : JSContext* cx, JS::HandleObject props_obj, GSignalMatchType* mask_out,
389 : : unsigned* signal_id_out, GQuark* detail_out,
390 : : JS::MutableHandleObject callable_out);
391 : :
392 : : public:
393 : 58 : static GObject* copy_ptr(JSContext*, GType, void* ptr) {
394 : 58 : return G_OBJECT(g_object_ref(G_OBJECT(ptr)));
395 : : }
396 : :
397 : : GJS_JSAPI_RETURN_CONVENTION
398 : : bool init_custom_class_from_gobject(JSContext* cx, JS::HandleObject wrapper,
399 : : GObject* gobj);
400 : :
401 : : static void associate_string(GObject* obj, char* str);
402 : :
403 : : /* Methods to manipulate the linked list of instances */
404 : :
405 : : private:
406 : : static std::vector<ObjectInstance*> s_wrapped_gobject_list;
407 : : void link(void);
408 : : void unlink(void);
409 : : [[nodiscard]] static size_t num_wrapped_gobjects() {
410 : : return s_wrapped_gobject_list.size();
411 : : }
412 : : using Action = std::function<void(ObjectInstance*)>;
413 : : using Predicate = std::function<bool(ObjectInstance*)>;
414 : : static void remove_wrapped_gobjects_if(const Predicate& predicate,
415 : : const Action& action);
416 : :
417 : : public:
418 : : static void prepare_shutdown(void);
419 : :
420 : : /* JSClass operations */
421 : :
422 : : private:
423 : : GJS_JSAPI_RETURN_CONVENTION
424 : : bool add_property_impl(JSContext* cx, JS::HandleObject obj, JS::HandleId id,
425 : : JS::HandleValue value);
426 : : void finalize_impl(JS::GCContext*, JSObject* obj);
427 : : void trace_impl(JSTracer* trc);
428 : :
429 : : /* JS property getters/setters */
430 : :
431 : : private:
432 : : GJS_JSAPI_RETURN_CONVENTION
433 : : bool prop_getter_impl(JSContext* cx, JS::HandleString name,
434 : : JS::MutableHandleValue rval);
435 : : GJS_JSAPI_RETURN_CONVENTION
436 : : bool field_getter_impl(JSContext* cx, JS::HandleString name,
437 : : JS::MutableHandleValue rval);
438 : : GJS_JSAPI_RETURN_CONVENTION
439 : : bool prop_setter_impl(JSContext* cx, JS::HandleString name,
440 : : JS::HandleValue value);
441 : : GJS_JSAPI_RETURN_CONVENTION
442 : : bool field_setter_not_impl(JSContext* cx, JS::HandleString name);
443 : :
444 : : // JS constructor
445 : :
446 : : GJS_JSAPI_RETURN_CONVENTION
447 : : bool constructor_impl(JSContext* cx, JS::HandleObject obj,
448 : : const JS::CallArgs& args);
449 : :
450 : : /* JS methods */
451 : :
452 : : private:
453 : : GJS_JSAPI_RETURN_CONVENTION
454 : : bool connect_impl(JSContext* cx, const JS::CallArgs& args, bool after);
455 : : GJS_JSAPI_RETURN_CONVENTION
456 : : bool emit_impl(JSContext* cx, const JS::CallArgs& args);
457 : : GJS_JSAPI_RETURN_CONVENTION
458 : : bool signal_find_impl(JSContext* cx, const JS::CallArgs& args);
459 : : template <SignalMatchFunc(*MATCH_FUNC)>
460 : : GJS_JSAPI_RETURN_CONVENTION bool signals_action_impl(
461 : : JSContext* cx, const JS::CallArgs& args);
462 : : GJS_JSAPI_RETURN_CONVENTION
463 : : bool init_impl(JSContext* cx, const JS::CallArgs& args,
464 : : JS::HandleObject obj);
465 : : [[nodiscard]] const char* to_string_kind() const;
466 : :
467 : : GJS_JSAPI_RETURN_CONVENTION
468 : : bool typecheck_impl(JSContext* cx, GIBaseInfo* expected_info,
469 : : GType expected_type) const;
470 : :
471 : : /* Notification callbacks */
472 : : void gobj_dispose_notify(void);
473 : : static void wrapped_gobj_dispose_notify(void* data, GObject*);
474 : : static void wrapped_gobj_toggle_notify(void* instance, GObject* gobj,
475 : : gboolean is_last_ref);
476 : :
477 : : public:
478 : : static void context_dispose_notify(void* data,
479 : : GObject* where_the_object_was);
480 : : };
481 : :
482 : : GJS_JSAPI_RETURN_CONVENTION
483 : : bool gjs_lookup_object_constructor(JSContext *context,
484 : : GType gtype,
485 : : JS::MutableHandleValue value_p);
486 : : GJS_JSAPI_RETURN_CONVENTION
487 : : JSObject* gjs_lookup_object_constructor_from_info(JSContext* cx,
488 : : GIObjectInfo* info,
489 : : GType gtype);
490 : :
491 : : void gjs_object_clear_toggles(void);
492 : : void gjs_object_shutdown_toggle_queue(void);
493 : :
494 : : #endif // GI_OBJECT_H_
|