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 : 2233 : explicit ObjectBase(ObjectPrototype* proto = nullptr)
68 : 2233 : : 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 : 7594 : void debug_lifecycle(const char* message) const {
100 : 7594 : GIWrapperBase::debug_lifecycle(jsobj_addr(), message);
101 : 7594 : }
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 : 280 : [[nodiscard]] static bool typecheck(JSContext* cx, JS::HandleObject obj,
113 : : GIObjectInfo* expected_info,
114 : : GType expected_gtype,
115 : : GjsTypecheckNoThrow no_throw) {
116 : 280 : return GIWrapperBase::typecheck(cx, obj, expected_info, expected_gtype,
117 : 280 : 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 connect_object(JSContext* cx, unsigned argc, JS::Value* vp);
145 : : GJS_JSAPI_RETURN_CONVENTION
146 : : static bool emit(JSContext* cx, unsigned argc, JS::Value* vp);
147 : : GJS_JSAPI_RETURN_CONVENTION
148 : : static bool signal_find(JSContext* cx, unsigned argc, JS::Value* vp);
149 : : template <SignalMatchFunc(*MATCH_FUNC)>
150 : : GJS_JSAPI_RETURN_CONVENTION static bool signals_action(JSContext* cx,
151 : : unsigned argc,
152 : : JS::Value* vp);
153 : : GJS_JSAPI_RETURN_CONVENTION
154 : : static bool to_string(JSContext* cx, unsigned argc, JS::Value* vp);
155 : : GJS_JSAPI_RETURN_CONVENTION
156 : : static bool init_gobject(JSContext* cx, unsigned argc, JS::Value* vp);
157 : : GJS_JSAPI_RETURN_CONVENTION
158 : : static bool hook_up_vfunc(JSContext* cx, unsigned argc, JS::Value* vp);
159 : :
160 : : /* Quarks */
161 : :
162 : : protected:
163 : : [[nodiscard]] static GQuark instance_strings_quark();
164 : :
165 : : public:
166 : : [[nodiscard]] static GQuark custom_type_quark();
167 : : [[nodiscard]] static GQuark custom_property_quark();
168 : : [[nodiscard]] static GQuark disposed_quark();
169 : : };
170 : :
171 : : // See https://bugzilla.mozilla.org/show_bug.cgi?id=1614220
172 : : struct IdHasher {
173 : : typedef jsid Lookup;
174 : 13656 : static mozilla::HashNumber hash(jsid id) {
175 [ + + ]: 13656 : if (MOZ_LIKELY(id.isString()))
176 : 13147 : return js::DefaultHasher<JSString*>::hash(id.toString());
177 [ + - ]: 509 : if (id.isSymbol())
178 : 509 : return js::DefaultHasher<JS::Symbol*>::hash(id.toSymbol());
179 : 0 : return mozilla::HashGeneric(id.asRawBits());
180 : : }
181 : 6556 : static bool match(jsid id1, jsid id2) { return id1 == id2; }
182 : : };
183 : :
184 : : class ObjectPrototype
185 : : : public GIWrapperPrototype<ObjectBase, ObjectPrototype, ObjectInstance> {
186 : : friend class GIWrapperPrototype<ObjectBase, ObjectPrototype,
187 : : ObjectInstance>;
188 : : friend class GIWrapperBase<ObjectBase, ObjectPrototype, ObjectInstance>;
189 : :
190 : : using FieldCache =
191 : : JS::GCHashMap<JS::Heap<JSString*>, GjsAutoFieldInfo,
192 : : js::DefaultHasher<JSString*>, js::SystemAllocPolicy>;
193 : : using NegativeLookupCache =
194 : : JS::GCHashSet<JS::Heap<jsid>, IdHasher, js::SystemAllocPolicy>;
195 : :
196 : : FieldCache m_field_cache;
197 : : NegativeLookupCache m_unresolvable_cache;
198 : : // a list of vfunc GClosures installed on this prototype, used when tracing
199 : : std::vector<GClosure*> m_vfuncs;
200 : : // a list of interface types explicitly associated with this prototype,
201 : : // by gjs_add_interface
202 : : std::vector<GType> m_interface_gtypes;
203 : :
204 : : ObjectPrototype(GIObjectInfo* info, GType gtype);
205 : : ~ObjectPrototype();
206 : :
207 : : static constexpr InfoType::Tag info_type_tag = InfoType::Object;
208 : :
209 : : public:
210 : : [[nodiscard]] static ObjectPrototype* for_gtype(GType gtype);
211 : :
212 : : /* Helper methods */
213 : : private:
214 : : GJS_JSAPI_RETURN_CONVENTION
215 : : bool get_parent_proto(JSContext* cx, JS::MutableHandleObject proto) const;
216 : : GJS_JSAPI_RETURN_CONVENTION
217 : : bool get_parent_constructor(JSContext* cx,
218 : : JS::MutableHandleObject constructor) const;
219 : :
220 : : [[nodiscard]] bool is_vfunc_unchanged(GIVFuncInfo* info);
221 : : static void vfunc_invalidated_notify(void* data, GClosure* closure);
222 : :
223 : : GJS_JSAPI_RETURN_CONVENTION
224 : : bool lazy_define_gobject_property(JSContext* cx, JS::HandleObject obj,
225 : : JS::HandleId id, GParamSpec*,
226 : : bool* resolved, const char* name);
227 : :
228 : : enum ResolveWhat { ConsiderOnlyMethods, ConsiderMethodsAndProperties };
229 : : GJS_JSAPI_RETURN_CONVENTION
230 : : bool resolve_no_info(JSContext* cx, JS::HandleObject obj, JS::HandleId id,
231 : : bool* resolved, const char* name,
232 : : ResolveWhat resolve_props);
233 : : GJS_JSAPI_RETURN_CONVENTION
234 : : bool uncached_resolve(JSContext* cx, JS::HandleObject obj, JS::HandleId id,
235 : : const char* name, bool* resolved);
236 : :
237 : : public:
238 : : void set_interfaces(GType* interface_gtypes, uint32_t n_interface_gtypes);
239 : : void set_type_qdata(void);
240 : : GJS_JSAPI_RETURN_CONVENTION
241 : : GParamSpec* find_param_spec_from_id(JSContext*,
242 : : GjsAutoTypeClass<GObjectClass> const&,
243 : : 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*,
248 : : GjsAutoTypeClass<GObjectClass> const&,
249 : : JS::HandleObject props,
250 : : std::vector<const char*>* names,
251 : : AutoGValueVector* values);
252 : :
253 : : GJS_JSAPI_RETURN_CONVENTION
254 : : static bool define_class(JSContext* cx, JS::HandleObject in_object,
255 : : GIObjectInfo* info, GType gtype,
256 : : GType* interface_gtypes,
257 : : uint32_t n_interface_gtypes,
258 : : JS::MutableHandleObject constructor,
259 : : JS::MutableHandleObject prototype);
260 : :
261 : 0 : void ref_vfuncs(void) {
262 [ # # ]: 0 : for (GClosure* closure : m_vfuncs)
263 : 0 : g_closure_ref(closure);
264 : 0 : }
265 : 0 : void unref_vfuncs(void) {
266 [ # # ]: 0 : for (GClosure* closure : m_vfuncs)
267 : 0 : g_closure_unref(closure);
268 : 0 : }
269 : :
270 : : /* JSClass operations */
271 : : private:
272 : : GJS_JSAPI_RETURN_CONVENTION
273 : : bool resolve_impl(JSContext* cx, JS::HandleObject obj, JS::HandleId id,
274 : : bool* resolved);
275 : :
276 : : GJS_JSAPI_RETURN_CONVENTION
277 : : bool new_enumerate_impl(JSContext* cx, JS::HandleObject obj,
278 : : JS::MutableHandleIdVector properties,
279 : : bool only_enumerable);
280 : : void trace_impl(JSTracer* tracer);
281 : :
282 : : /* JS methods */
283 : : public:
284 : : GJS_JSAPI_RETURN_CONVENTION
285 : : bool hook_up_vfunc_impl(JSContext* cx, const JS::CallArgs& args);
286 : : };
287 : :
288 : : class ObjectInstance : public GIWrapperInstance<ObjectBase, ObjectPrototype,
289 : : ObjectInstance, GObject> {
290 : : friend class GIWrapperInstance<ObjectBase, ObjectPrototype, ObjectInstance,
291 : : GObject>;
292 : : friend class GIWrapperBase<ObjectBase, ObjectPrototype, ObjectInstance>;
293 : : friend class ObjectBase; // for add_property, prop_getter, etc.
294 : : friend struct Gjs::Test::ObjectInstance;
295 : :
296 : : // GIWrapperInstance::m_ptr may be null in ObjectInstance.
297 : :
298 : : GjsMaybeOwned m_wrapper;
299 : : // a list of all GClosures installed on this object (from signal connections
300 : : // and scope-notify callbacks passed to methods), used when tracing
301 : : std::vector<GClosure*> m_closures;
302 : :
303 : : bool m_wrapper_finalized : 1;
304 : : bool m_gobj_disposed : 1;
305 : : bool m_gobj_finalized : 1;
306 : :
307 : : /* True if this object has visible JS state, and thus its lifecycle is
308 : : * managed using toggle references. False if this object just keeps a
309 : : * hard ref on the underlying GObject, and may be finalized at will. */
310 : : bool m_uses_toggle_ref : 1;
311 : :
312 : : static bool s_weak_pointer_callback;
313 : :
314 : : /* Constructors */
315 : :
316 : : private:
317 : : ObjectInstance(ObjectPrototype* prototype, JS::HandleObject obj);
318 : : ~ObjectInstance();
319 : :
320 : : GJS_JSAPI_RETURN_CONVENTION
321 : : static ObjectInstance* new_for_gobject(JSContext* cx, GObject* gobj);
322 : :
323 : : // Extra method to get an existing ObjectInstance from qdata
324 : :
325 : : public:
326 : : [[nodiscard]] static ObjectInstance* for_gobject(GObject* gobj);
327 : :
328 : : /* Accessors */
329 : :
330 : : private:
331 : 3180 : [[nodiscard]] bool has_wrapper() const { return !!m_wrapper; }
332 : :
333 : : public:
334 : 2717 : [[nodiscard]] JSObject* wrapper() const { return m_wrapper.get(); }
335 : :
336 : : /* Methods to manipulate the JS object wrapper */
337 : :
338 : : private:
339 : 1839 : void discard_wrapper(void) { m_wrapper.reset(); }
340 : 1607 : void switch_to_rooted(JSContext* cx) { m_wrapper.switch_to_rooted(cx); }
341 : 1466 : void switch_to_unrooted(JSContext* cx) { m_wrapper.switch_to_unrooted(cx); }
342 : 1538 : [[nodiscard]] bool update_after_gc(JSTracer* trc) {
343 : 1538 : return m_wrapper.update_after_gc(trc);
344 : : }
345 : 9083 : [[nodiscard]] bool wrapper_is_rooted() const { return m_wrapper.rooted(); }
346 : : void release_native_object(void);
347 : : void associate_js_gobject(JSContext* cx, JS::HandleObject obj,
348 : : GObject* gobj);
349 : : void disassociate_js_gobject(void);
350 : : void handle_context_dispose(void);
351 : : [[nodiscard]] bool weak_pointer_was_finalized(JSTracer* trc);
352 : : static void ensure_weak_pointer_callback(JSContext* cx);
353 : : static void update_heap_wrapper_weak_pointers(JSTracer* trc,
354 : : JS::Compartment*, void* data);
355 : :
356 : : public:
357 : : void toggle_down(void);
358 : : void toggle_up(void);
359 : :
360 : : GJS_JSAPI_RETURN_CONVENTION
361 : : static JSObject* wrapper_from_gobject(JSContext* cx, GObject* ptr);
362 : :
363 : : GJS_JSAPI_RETURN_CONVENTION
364 : : static bool set_value_from_gobject(JSContext* cx, GObject*,
365 : : JS::MutableHandleValue);
366 : :
367 : : /* Methods to manipulate the list of closures */
368 : :
369 : : private:
370 : : void invalidate_closures();
371 : : static void closure_invalidated_notify(void* data, GClosure* closure);
372 : :
373 : : public:
374 : : GJS_JSAPI_RETURN_CONVENTION bool associate_closure(JSContext*, GClosure*);
375 : :
376 : : /* Helper methods */
377 : :
378 : : private:
379 : : void set_object_qdata(void);
380 : : void unset_object_qdata(void);
381 : : void track_gobject_finalization();
382 : : void ignore_gobject_finalization();
383 : : void check_js_object_finalized(void);
384 : : void ensure_uses_toggle_ref(JSContext*);
385 : : [[nodiscard]] bool check_gobject_disposed_or_finalized(
386 : : const char* for_what) const;
387 : : [[nodiscard]] bool check_gobject_finalized(const char* for_what) const;
388 : : GJS_JSAPI_RETURN_CONVENTION
389 : : bool signal_match_arguments_from_object(
390 : : JSContext* cx, JS::HandleObject props_obj, GSignalMatchType* mask_out,
391 : : unsigned* signal_id_out, GQuark* detail_out,
392 : : JS::MutableHandleObject callable_out);
393 : :
394 : : public:
395 : 58 : static GObject* copy_ptr(JSContext*, GType, void* ptr) {
396 : 58 : return G_OBJECT(g_object_ref(G_OBJECT(ptr)));
397 : : }
398 : :
399 : : GJS_JSAPI_RETURN_CONVENTION
400 : : bool init_custom_class_from_gobject(JSContext* cx, JS::HandleObject wrapper,
401 : : GObject* gobj);
402 : :
403 : : static void associate_string(GObject* obj, char* str);
404 : :
405 : : /* Methods to manipulate the linked list of instances */
406 : :
407 : : private:
408 : : static std::vector<ObjectInstance*> s_wrapped_gobject_list;
409 : : void link(void);
410 : : void unlink(void);
411 : : [[nodiscard]] static size_t num_wrapped_gobjects() {
412 : : return s_wrapped_gobject_list.size();
413 : : }
414 : : using Action = std::function<void(ObjectInstance*)>;
415 : : using Predicate = std::function<bool(ObjectInstance*)>;
416 : : static void remove_wrapped_gobjects_if(const Predicate& predicate,
417 : : const Action& action);
418 : :
419 : : public:
420 : : static void prepare_shutdown(void);
421 : :
422 : : /* JSClass operations */
423 : :
424 : : private:
425 : : GJS_JSAPI_RETURN_CONVENTION
426 : : bool add_property_impl(JSContext* cx, JS::HandleObject obj, JS::HandleId id,
427 : : JS::HandleValue value);
428 : : void finalize_impl(JS::GCContext*, JSObject* obj);
429 : : void trace_impl(JSTracer* trc);
430 : :
431 : : /* JS property getters/setters */
432 : :
433 : : private:
434 : : GJS_JSAPI_RETURN_CONVENTION
435 : : bool prop_getter_impl(JSContext* cx, GParamSpec*,
436 : : JS::MutableHandleValue rval);
437 : : GJS_JSAPI_RETURN_CONVENTION
438 : : bool field_getter_impl(JSContext* cx, JS::HandleString name,
439 : : JS::MutableHandleValue rval);
440 : : GJS_JSAPI_RETURN_CONVENTION
441 : : bool prop_setter_impl(JSContext* cx, GParamSpec*, JS::HandleValue value);
442 : : GJS_JSAPI_RETURN_CONVENTION
443 : : bool field_setter_not_impl(JSContext* cx, JS::HandleString name);
444 : :
445 : : // JS constructor
446 : :
447 : : GJS_JSAPI_RETURN_CONVENTION
448 : : bool constructor_impl(JSContext* cx, JS::HandleObject obj,
449 : : const JS::CallArgs& args);
450 : :
451 : : /* JS methods */
452 : :
453 : : private:
454 : : GJS_JSAPI_RETURN_CONVENTION
455 : : bool connect_impl(JSContext* cx, const JS::CallArgs& args, bool after,
456 : : bool object = false);
457 : : GJS_JSAPI_RETURN_CONVENTION
458 : : bool emit_impl(JSContext* cx, const JS::CallArgs& args);
459 : : GJS_JSAPI_RETURN_CONVENTION
460 : : bool signal_find_impl(JSContext* cx, const JS::CallArgs& args);
461 : : template <SignalMatchFunc(*MATCH_FUNC)>
462 : : GJS_JSAPI_RETURN_CONVENTION bool signals_action_impl(
463 : : JSContext* cx, const JS::CallArgs& args);
464 : : GJS_JSAPI_RETURN_CONVENTION
465 : : bool init_impl(JSContext* cx, const JS::CallArgs& args,
466 : : JS::HandleObject obj);
467 : : [[nodiscard]] const char* to_string_kind() const;
468 : :
469 : : GJS_JSAPI_RETURN_CONVENTION
470 : : bool typecheck_impl(JSContext* cx, GIBaseInfo* expected_info,
471 : : GType expected_type) const;
472 : :
473 : : /* Notification callbacks */
474 : : void gobj_dispose_notify(void);
475 : : static void wrapped_gobj_dispose_notify(void* data, GObject*);
476 : : static void wrapped_gobj_toggle_notify(void* instance, GObject* gobj,
477 : : gboolean is_last_ref);
478 : :
479 : : public:
480 : : static void context_dispose_notify(void* data,
481 : : GObject* where_the_object_was);
482 : : };
483 : :
484 : : GJS_JSAPI_RETURN_CONVENTION
485 : : bool gjs_lookup_object_constructor(JSContext *context,
486 : : GType gtype,
487 : : JS::MutableHandleValue value_p);
488 : :
489 : : void gjs_object_clear_toggles(void);
490 : : void gjs_object_shutdown_toggle_queue(void);
491 : :
492 : : #endif // GI_OBJECT_H_
|