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 <stddef.h> // for size_t
8 : :
9 : : #include <glib.h>
10 : :
11 : : #include <js/CallArgs.h>
12 : : #include <js/Class.h>
13 : : #include <js/ErrorReport.h> // for JSEXN_TYPEERR
14 : : #include <js/Object.h> // for GetClass
15 : : #include <js/PropertyAndElement.h>
16 : : #include <js/PropertyDescriptor.h> // for JSPROP_READONLY
17 : : #include <js/PropertySpec.h> // for JSPropertySpec, JS_PS_END, JS_STR...
18 : : #include <js/RootingAPI.h>
19 : : #include <js/TypeDecls.h>
20 : : #include <js/Utility.h> // for UniqueChars
21 : : #include <js/Value.h>
22 : : #include <jsapi.h> // for JS_NewObjectForConstructor, JS_NewObjectWithG...
23 : : #include <mozilla/Maybe.h>
24 : :
25 : : #include "gi/cwrapper.h"
26 : : #include "gi/function.h"
27 : : #include "gi/info.h"
28 : : #include "gi/param.h"
29 : : #include "gi/repo.h"
30 : : #include "gi/wrapperutils.h"
31 : : #include "gjs/atoms.h"
32 : : #include "gjs/auto.h"
33 : : #include "gjs/context-private.h"
34 : : #include "gjs/jsapi-class.h"
35 : : #include "gjs/jsapi-util.h"
36 : : #include "gjs/macros.h"
37 : : #include "gjs/mem-private.h"
38 : : #include "util/log.h"
39 : :
40 : : using mozilla::Maybe;
41 : :
42 : : extern struct JSClass gjs_param_class;
43 : :
44 : : // Reserved slots
45 : : static const size_t POINTER = 0;
46 : :
47 : : struct Param : Gjs::AutoParam {
48 : 298 : explicit Param(GParamSpec* param)
49 : 298 : : Gjs::AutoParam(param, Gjs::TakeOwnership{}) {}
50 : : };
51 : :
52 : : [[nodiscard]]
53 : 1918 : static GParamSpec* param_value(JSContext* cx, JS::HandleObject obj) {
54 [ - + ]: 1918 : if (!JS_InstanceOf(cx, obj, &gjs_param_class, nullptr))
55 : 0 : return nullptr;
56 : :
57 : 1918 : auto* priv = JS::GetMaybePtrFromReservedSlot<Param>(obj, POINTER);
58 [ + + ]: 1918 : return priv ? priv->get() : nullptr;
59 : : }
60 : :
61 : : /*
62 : : * The *resolved out parameter, on success, should be false to indicate that id
63 : : * was not resolved; and true if id was resolved.
64 : : */
65 : : GJS_JSAPI_RETURN_CONVENTION
66 : 1218 : static bool param_resolve(JSContext* cx, JS::HandleObject obj, JS::HandleId id,
67 : : bool* resolved) {
68 [ + + ]: 1218 : if (!param_value(cx, obj)) {
69 : : // instance, not prototype
70 : 695 : *resolved = false;
71 : 695 : return true;
72 : : }
73 : :
74 : 523 : JS::UniqueChars name;
75 [ - + ]: 523 : if (!gjs_get_string_id(cx, id, &name))
76 : 0 : return false;
77 [ + + ]: 523 : if (!name) {
78 : 20 : *resolved = false;
79 : 20 : return true; // not resolved, but no error
80 : : }
81 : :
82 : 503 : GI::Repository repo;
83 : : GI::AutoObjectInfo info{
84 : 503 : repo.find_by_gtype<GI::InfoTag::OBJECT>(G_TYPE_PARAM).value()};
85 : 503 : Maybe<GI::AutoFunctionInfo> method_info{info.method(name.get())};
86 : :
87 [ + + ]: 503 : if (!method_info) {
88 : 375 : *resolved = false;
89 : 375 : return true;
90 : : }
91 : 128 : method_info->log_usage();
92 : :
93 [ + - ]: 128 : if (method_info->is_method()) {
94 : 128 : gjs_debug(GJS_DEBUG_GOBJECT,
95 : : "Defining method %s in prototype for GObject.ParamSpec",
96 : 128 : method_info->name());
97 : :
98 [ - + ]: 128 : if (!gjs_define_function(cx, obj, G_TYPE_PARAM, method_info.ref()))
99 : 0 : return false;
100 : :
101 : 128 : *resolved = true; // we defined the prop in obj
102 : : }
103 : :
104 : 128 : return true;
105 : 523 : }
106 : :
107 : : GJS_JSAPI_RETURN_CONVENTION
108 : 0 : static bool gjs_param_constructor(JSContext* cx, unsigned argc, JS::Value* vp) {
109 : 0 : JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
110 : :
111 [ # # ]: 0 : if (!args.isConstructing()) {
112 : 0 : gjs_throw_constructor_error(cx);
113 : 0 : return false;
114 : : }
115 : :
116 : : JS::RootedObject new_object(
117 : 0 : cx, JS_NewObjectForConstructor(cx, &gjs_param_class, args));
118 [ # # ]: 0 : if (!new_object)
119 : 0 : return false;
120 : :
121 : 0 : GJS_INC_COUNTER(param);
122 : :
123 : 0 : args.rval().setObject(*new_object);
124 : 0 : return true;
125 : 0 : }
126 : :
127 : 356 : static void param_finalize(JS::GCContext*, JSObject* obj) {
128 : 356 : Param* priv = JS::GetMaybePtrFromReservedSlot<Param>(obj, POINTER);
129 : : gjs_debug_lifecycle(GJS_DEBUG_GPARAM, "finalize, obj %p priv %p", obj,
130 : : priv);
131 [ + + ]: 356 : if (!priv)
132 : 58 : return; // wrong class?
133 : :
134 : 298 : GJS_DEC_COUNTER(param);
135 : 298 : JS::SetReservedSlot(obj, POINTER, JS::UndefinedValue());
136 [ + - ]: 298 : delete priv;
137 : : }
138 : :
139 : : /* The bizarre thing about this vtable is that it applies to both
140 : : * instances of the object, and to the prototype that instances of the
141 : : * class have.
142 : : */
143 : : static const struct JSClassOps gjs_param_class_ops = {
144 : : nullptr, // addProperty
145 : : nullptr, // deleteProperty
146 : : nullptr, // enumerate
147 : : nullptr, // newEnumerate
148 : : param_resolve,
149 : : nullptr, // mayResolve
150 : : param_finalize};
151 : :
152 : : static JSPropertySpec proto_props[] = {
153 : : JS_STRING_SYM_PS(toStringTag, "GObject_ParamSpec", JSPROP_READONLY),
154 : : JS_PS_END};
155 : :
156 : : static constexpr js::ClassSpec class_spec = {
157 : : nullptr, // createConstructor
158 : : nullptr, // createPrototype
159 : : nullptr, // constructorFunctions
160 : : nullptr, // constructorProperties
161 : : nullptr, // prototypeFunctions
162 : : proto_props, // prototypeProperties
163 : : nullptr // finishInit
164 : : };
165 : :
166 : : struct JSClass gjs_param_class = {
167 : : "GObject_ParamSpec",
168 : : JSCLASS_HAS_RESERVED_SLOTS(1) | JSCLASS_BACKGROUND_FINALIZE,
169 : : &gjs_param_class_ops, &class_spec};
170 : :
171 : : GJS_JSAPI_RETURN_CONVENTION
172 : 298 : static JSObject* gjs_lookup_param_prototype(JSContext* cx) {
173 : 298 : const GjsAtoms& atoms = GjsContextPrivate::atoms(cx);
174 : : JS::RootedObject in_object{
175 : 298 : cx, gjs_lookup_namespace_object_by_name(cx, atoms.gobject())};
176 : :
177 [ - + ]: 298 : if (G_UNLIKELY (!in_object))
178 : 0 : return nullptr;
179 : :
180 : 298 : JS::RootedValue value{cx};
181 [ + - - + ]: 596 : if (!JS_GetPropertyById(cx, in_object, atoms.param_spec(), &value) ||
182 [ - + ]: 298 : G_UNLIKELY(!value.isObject()))
183 : 0 : return nullptr;
184 : :
185 : 298 : JS::RootedObject constructor{cx, &value.toObject()};
186 : 298 : g_assert(constructor);
187 : :
188 [ + - - + ]: 596 : if (!JS_GetPropertyById(cx, constructor, atoms.prototype(), &value) ||
189 [ - + ]: 298 : G_UNLIKELY(!value.isObjectOrNull()))
190 : 0 : return nullptr;
191 : :
192 : 298 : return value.toObjectOrNull();
193 : 298 : }
194 : :
195 : 59 : bool gjs_define_param_class(JSContext* cx, JS::HandleObject in_object) {
196 : 59 : JS::RootedObject prototype{cx}, constructor{cx};
197 [ - + ]: 59 : if (!gjs_init_class_dynamic(
198 : 59 : cx, in_object, nullptr, "GObject", "ParamSpec", &gjs_param_class,
199 : : gjs_param_constructor, 0,
200 : : proto_props, // props of prototype
201 : : nullptr, // funcs of prototype
202 : : nullptr, // props of constructor, MyConstructor.myprop
203 : : nullptr, // funcs of constructor
204 : 59 : &prototype, &constructor))
205 : 0 : return false;
206 : :
207 [ - + ]: 59 : if (!gjs_wrapper_define_gtype_prop(cx, constructor, G_TYPE_PARAM))
208 : 0 : return false;
209 : :
210 : 59 : GI::Repository repo;
211 : : GI::AutoObjectInfo info{
212 : 59 : repo.find_by_gtype<GI::InfoTag::OBJECT>(G_TYPE_PARAM).value()};
213 [ - + ]: 59 : if (!gjs_define_static_methods(cx, constructor, G_TYPE_PARAM, info))
214 : 0 : return false;
215 : :
216 : 59 : gjs_debug(GJS_DEBUG_GPARAM,
217 : : "Defined class ParamSpec prototype is %p class %p in object %p",
218 : 59 : prototype.get(), &gjs_param_class, in_object.get());
219 : 59 : return true;
220 : 59 : }
221 : :
222 : 298 : JSObject* gjs_param_from_g_param(JSContext* cx, GParamSpec* gparam) {
223 [ - + ]: 298 : if (!gparam)
224 : 0 : return nullptr;
225 : :
226 : 596 : gjs_debug(GJS_DEBUG_GPARAM,
227 : : "Wrapping %s '%s' on %s with JSObject",
228 : 298 : g_type_name(G_TYPE_FROM_INSTANCE((GTypeInstance*) gparam)),
229 : : gparam->name,
230 : : g_type_name(gparam->owner_type));
231 : :
232 : 298 : JS::RootedObject proto{cx, gjs_lookup_param_prototype(cx)};
233 [ - + ]: 298 : if (!proto)
234 : 0 : return nullptr;
235 : :
236 : 298 : JSObject* obj = JS_NewObjectWithGivenProto(cx, JS::GetClass(proto), proto);
237 [ - + ]: 298 : if (!obj)
238 : 0 : return nullptr;
239 : :
240 : 298 : GJS_INC_COUNTER(param);
241 : 298 : auto* priv = new Param(gparam);
242 : 298 : JS::SetReservedSlot(obj, POINTER, JS::PrivateValue(priv));
243 : :
244 : 298 : gjs_debug(GJS_DEBUG_GPARAM,
245 : : "JSObject created with param instance %p type %s", gparam,
246 : 298 : g_type_name(G_TYPE_FROM_INSTANCE(gparam)));
247 : :
248 : 298 : return obj;
249 : 298 : }
250 : :
251 : 350 : GParamSpec* gjs_g_param_from_param(JSContext* cx, JS::HandleObject obj) {
252 [ - + ]: 350 : if (!obj)
253 : 0 : return nullptr;
254 : :
255 : 350 : return param_value(cx, obj);
256 : : }
257 : :
258 : 350 : bool gjs_typecheck_param(JSContext* cx, JS::HandleObject object,
259 : : GType expected_type, bool throw_error) {
260 [ - + ]: 350 : if (!gjs_typecheck_instance(cx, object, &gjs_param_class, throw_error))
261 : 0 : return false;
262 : :
263 : 350 : GParamSpec* param = param_value(cx, object);
264 [ - + ]: 350 : if (!param) {
265 [ # # ]: 0 : if (throw_error) {
266 : 0 : gjs_throw_custom(cx, JSEXN_TYPEERR, nullptr,
267 : : "Object is GObject.ParamSpec.prototype, not an "
268 : : "object instance - cannot convert to a GObject."
269 : : "ParamSpec instance");
270 : : }
271 : :
272 : 0 : return false;
273 : : }
274 : :
275 [ + + ]: 350 : if (expected_type == G_TYPE_NONE)
276 : 68 : return true;
277 : :
278 [ + - - + : 282 : if (!g_type_is_a(G_TYPE_FROM_INSTANCE(param), expected_type)) {
- + ]
279 [ # # ]: 0 : if (throw_error) {
280 : 0 : gjs_throw_custom(cx, JSEXN_TYPEERR, nullptr,
281 : : "Object is of type %s - cannot convert to %s",
282 : 0 : g_type_name(G_TYPE_FROM_INSTANCE(param)),
283 : : g_type_name(expected_type));
284 : : }
285 : 0 : return false;
286 : : }
287 : :
288 : 282 : return true;
289 : : }
|