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 : : // SPDX-FileCopyrightText: 2012 Red Hat, Inc.
5 : :
6 : : #include <config.h>
7 : :
8 : : #include <glib-object.h>
9 : : #include <glib.h>
10 : :
11 : : #include <js/CallArgs.h>
12 : : #include <js/Class.h>
13 : : #include <js/GCHashTable.h> // for WeakCache
14 : : #include <js/PropertyAndElement.h>
15 : : #include <js/PropertyDescriptor.h> // for JSPROP_PERMANENT
16 : : #include <js/PropertySpec.h>
17 : : #include <js/RootingAPI.h>
18 : : #include <js/TypeDecls.h>
19 : : #include <js/Value.h>
20 : : #include <jsapi.h> // for JS_NewObjectWithGivenProto
21 : : #include <mozilla/HashTable.h>
22 : :
23 : : #include "gi/cwrapper.h"
24 : : #include "gi/gtype.h"
25 : : #include "gjs/atoms.h"
26 : : #include "gjs/auto.h"
27 : : #include "gjs/context-private.h"
28 : : #include "gjs/global.h"
29 : : #include "gjs/jsapi-util-root.h" // for WeakPtr methods
30 : : #include "gjs/jsapi-util.h"
31 : : #include "gjs/macros.h"
32 : : #include "util/log.h"
33 : :
34 : : /*
35 : : * GTypeObj:
36 : : *
37 : : * Wrapper object used to represent a GType in JavaScript.
38 : : * In C, GTypes are just a pointer-sized integer, but in JS they have a 'name'
39 : : * property and a toString() method.
40 : : * The integer is stuffed into CWrapper's pointer slot.
41 : : */
42 : : class GTypeObj : public CWrapper<GTypeObj, void> {
43 : : friend CWrapperPointerOps<GTypeObj, void>;
44 : : friend CWrapper<GTypeObj, void>;
45 : :
46 : : static constexpr auto PROTOTYPE_SLOT = GjsGlobalSlot::PROTOTYPE_gtype;
47 : : static constexpr GjsDebugTopic DEBUG_TOPIC = GJS_DEBUG_GTYPE;
48 : :
49 : : // JSClass operations
50 : :
51 : : // No private data is allocated, it's stuffed directly in the private field
52 : : // of JSObject, so nothing to free
53 : 2601 : static void finalize_impl(JS::GCContext*, void*) {}
54 : :
55 : : // Properties
56 : :
57 : : GJS_JSAPI_RETURN_CONVENTION
58 : 43 : static bool get_name(JSContext* cx, unsigned argc, JS::Value* vp) {
59 [ - + ]: 43 : GJS_GET_THIS(cx, argc, vp, args, obj);
60 : 43 : GType gtype = value(cx, obj, &args);
61 [ - + ]: 43 : if (gtype == 0)
62 : 0 : return false;
63 : :
64 : 43 : return gjs_string_from_utf8(cx, g_type_name(gtype), args.rval());
65 : 43 : }
66 : :
67 : : // Methods
68 : :
69 : : GJS_JSAPI_RETURN_CONVENTION
70 : 47 : static bool to_string(JSContext* cx, unsigned argc, JS::Value* vp) {
71 [ - + ]: 47 : GJS_GET_THIS(cx, argc, vp, rec, obj);
72 : 47 : GType gtype = value(cx, obj, &rec);
73 [ - + ]: 47 : if (gtype == 0)
74 : 0 : return false;
75 : :
76 : : Gjs::AutoChar strval{
77 : 47 : g_strdup_printf("[object GType for '%s']", g_type_name(gtype))};
78 : 47 : return gjs_string_from_utf8(cx, strval, rec.rval());
79 : 47 : }
80 : :
81 : : // clang-format off
82 : : static constexpr JSPropertySpec proto_props[] = {
83 : : JS_PSG("name", >ypeObj::get_name, JSPROP_PERMANENT),
84 : : JS_STRING_SYM_PS(toStringTag, "GIRepositoryGType", JSPROP_READONLY),
85 : : JS_PS_END};
86 : :
87 : : static constexpr JSFunctionSpec proto_funcs[] = {
88 : : JS_FN("toString", >ypeObj::to_string, 0, 0),
89 : : JS_FS_END};
90 : : // clang-format on
91 : :
92 : : static constexpr js::ClassSpec class_spec = {
93 : : nullptr, // createConstructor
94 : : nullptr, // createPrototype
95 : : nullptr, // constructorFunctions
96 : : nullptr, // constructorProperties
97 : : GTypeObj::proto_funcs,
98 : : GTypeObj::proto_props,
99 : : nullptr, // finishInit
100 : : js::ClassSpec::DontDefineConstructor};
101 : :
102 : : static constexpr JSClass klass = {
103 : : "GIRepositoryGType",
104 : : JSCLASS_HAS_RESERVED_SLOTS(1) | JSCLASS_FOREGROUND_FINALIZE,
105 : : >ypeObj::class_ops, >ypeObj::class_spec};
106 : :
107 : : GJS_JSAPI_RETURN_CONVENTION
108 : 90 : static GType value(JSContext* cx, JS::HandleObject obj,
109 : : JS::CallArgs* args) {
110 : : void* data;
111 [ - + ]: 90 : if (!for_js_typecheck(cx, obj, &data, args))
112 : 0 : return G_TYPE_NONE;
113 : 90 : return GPOINTER_TO_SIZE(data);
114 : : }
115 : :
116 : : GJS_JSAPI_RETURN_CONVENTION
117 : 1369 : static GType value(JSContext* cx, JS::HandleObject obj) {
118 : 1369 : return GPOINTER_TO_SIZE(for_js(cx, obj));
119 : : }
120 : :
121 : : GJS_JSAPI_RETURN_CONVENTION
122 : 1369 : static bool actual_gtype_recurse(JSContext* cx, const GjsAtoms& atoms,
123 : : JS::HandleObject object, GType* gtype_out,
124 : : int recurse) {
125 : 1369 : GType gtype = value(cx, object);
126 [ + + ]: 1369 : if (gtype > 0) {
127 : 624 : *gtype_out = gtype;
128 : 624 : return true;
129 : : }
130 : :
131 : 745 : JS::RootedValue v_gtype(cx);
132 : :
133 : : // OK, we don't have a GType wrapper object -- grab the "$gtype"
134 : : // property on that and hope it's a GType wrapper object
135 [ - + ]: 745 : if (!JS_GetPropertyById(cx, object, atoms.gtype(), &v_gtype))
136 : 0 : return false;
137 [ + + ]: 745 : if (!v_gtype.isObject()) {
138 : : // OK, so we're not a class. But maybe we're an instance. Check for
139 : : // "constructor" and recurse on that.
140 [ - + ]: 261 : if (!JS_GetPropertyById(cx, object, atoms.constructor(), &v_gtype))
141 : 0 : return false;
142 : : }
143 : :
144 [ + + + - : 745 : if (recurse > 0 && v_gtype.isObject()) {
+ + ]
145 : 707 : JS::RootedObject gtype_obj(cx, &v_gtype.toObject());
146 : 707 : return actual_gtype_recurse(cx, atoms, gtype_obj, gtype_out,
147 : 707 : recurse - 1);
148 : 707 : }
149 : :
150 : 38 : *gtype_out = G_TYPE_INVALID;
151 : 38 : return true;
152 : 745 : }
153 : :
154 : : public:
155 : : GJS_JSAPI_RETURN_CONVENTION
156 : 3001 : static JSObject* create(JSContext* cx, GType gtype) {
157 : 3001 : g_assert(gtype != 0 &&
158 : : "Attempted to create wrapper object for invalid GType");
159 : :
160 : 3001 : GjsContextPrivate* gjs = GjsContextPrivate::from_cx(cx);
161 : : // We cannot use gtype_table().lookupForAdd() here, because in between
162 : : // the lookup and the add, GCs may take place and mutate the hash table.
163 : : // A GC may only remove an element, not add one, so it's still safe to
164 : : // do this without locking.
165 : 3001 : auto p = gjs->gtype_table().lookup(gtype);
166 [ + + ]: 3001 : if (p.found())
167 : 359 : return p->value();
168 : :
169 : 2642 : JS::RootedObject proto(cx, GTypeObj::create_prototype(cx));
170 [ - + ]: 2642 : if (!proto)
171 : 0 : return nullptr;
172 : :
173 : : JS::RootedObject gtype_wrapper(
174 : 2642 : cx, JS_NewObjectWithGivenProto(cx, >ypeObj::klass, proto));
175 [ - + ]: 2642 : if (!gtype_wrapper)
176 : 0 : return nullptr;
177 : :
178 : 2642 : GTypeObj::init_private(gtype_wrapper, GSIZE_TO_POINTER(gtype));
179 : :
180 : 2642 : gjs->gtype_table().put(gtype, gtype_wrapper);
181 : :
182 : 2642 : return gtype_wrapper;
183 : 2642 : }
184 : :
185 : : GJS_JSAPI_RETURN_CONVENTION
186 : 662 : static bool actual_gtype(JSContext* cx, JS::HandleObject object,
187 : : GType* gtype_out) {
188 : 662 : g_assert(gtype_out && "Missing return location");
189 : :
190 : : // 2 means: recurse at most three times (including this call).
191 : : // The levels are calculated considering that, in the worst case we need
192 : : // to go from instance to class, from class to GType object and from
193 : : // GType object to GType value.
194 : :
195 : 662 : const GjsAtoms& atoms = GjsContextPrivate::atoms(cx);
196 : 662 : return actual_gtype_recurse(cx, atoms, object, gtype_out, 2);
197 : : }
198 : : };
199 : :
200 : 3001 : JSObject* gjs_gtype_create_gtype_wrapper(JSContext* context, GType gtype) {
201 : 3001 : return GTypeObj::create(context, gtype);
202 : : }
203 : :
204 : 662 : bool gjs_gtype_get_actual_gtype(JSContext* context, JS::HandleObject object,
205 : : GType* gtype_out) {
206 : 662 : return GTypeObj::actual_gtype(context, object, gtype_out);
207 : : }
|