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 <girepository.h>
8 : : #include <glib.h>
9 : :
10 : : #include <js/CallArgs.h>
11 : : #include <js/Class.h>
12 : : #include <js/ComparisonOperators.h>
13 : : #include <js/ErrorReport.h> // for JS_ReportOutOfMemory
14 : : #include <js/Id.h>
15 : : #include <js/PropertyDescriptor.h> // for JSPROP_READONLY
16 : : #include <js/PropertySpec.h>
17 : : #include <js/RootingAPI.h>
18 : : #include <js/TypeDecls.h>
19 : : #include <js/Utility.h> // for UniqueChars
20 : : #include <jsapi.h> // for JS_NewObjectWithGivenProto
21 : :
22 : : #include "gi/cwrapper.h"
23 : : #include "gi/ns.h"
24 : : #include "gi/repo.h"
25 : : #include "gjs/atoms.h"
26 : : #include "gjs/context-private.h"
27 : : #include "gjs/global.h"
28 : : #include "gjs/jsapi-util.h"
29 : : #include "gjs/macros.h"
30 : : #include "gjs/mem-private.h"
31 : : #include "util/log.h"
32 : :
33 : 5016 : [[nodiscard]] static bool type_is_enumerable(GIInfoType info_type) {
34 [ + + ]: 5016 : switch (info_type) {
35 : 5006 : case GI_INFO_TYPE_BOXED:
36 : : case GI_INFO_TYPE_STRUCT:
37 : : case GI_INFO_TYPE_UNION:
38 : : case GI_INFO_TYPE_OBJECT:
39 : : case GI_INFO_TYPE_ENUM:
40 : : case GI_INFO_TYPE_FLAGS:
41 : : case GI_INFO_TYPE_INTERFACE:
42 : : case GI_INFO_TYPE_FUNCTION:
43 : : case GI_INFO_TYPE_CONSTANT:
44 : 5006 : return true;
45 : : // Don't enumerate types which GJS doesn't define on namespaces.
46 : : // See gjs_define_info
47 : 10 : case GI_INFO_TYPE_INVALID:
48 : : case GI_INFO_TYPE_INVALID_0:
49 : : case GI_INFO_TYPE_CALLBACK:
50 : : case GI_INFO_TYPE_VALUE:
51 : : case GI_INFO_TYPE_SIGNAL:
52 : : case GI_INFO_TYPE_VFUNC:
53 : : case GI_INFO_TYPE_PROPERTY:
54 : : case GI_INFO_TYPE_FIELD:
55 : : case GI_INFO_TYPE_ARG:
56 : : case GI_INFO_TYPE_TYPE:
57 : : case GI_INFO_TYPE_UNRESOLVED:
58 : : default:
59 : 10 : return false;
60 : : }
61 : : }
62 : :
63 : : class Ns : private GjsAutoChar, public CWrapper<Ns> {
64 : : friend CWrapperPointerOps<Ns>;
65 : : friend CWrapper<Ns>;
66 : :
67 : : static constexpr auto PROTOTYPE_SLOT = GjsGlobalSlot::PROTOTYPE_ns;
68 : : static constexpr GjsDebugTopic DEBUG_TOPIC = GJS_DEBUG_GNAMESPACE;
69 : :
70 : 240 : explicit Ns(const char* ns_name)
71 : 240 : : GjsAutoChar(const_cast<char*>(ns_name), GjsAutoTakeOwnership()) {
72 : 240 : GJS_INC_COUNTER(ns);
73 : 240 : }
74 : :
75 : 236 : ~Ns() { GJS_DEC_COUNTER(ns); }
76 : :
77 : : // JSClass operations
78 : :
79 : : // The *resolved out parameter, on success, should be false to indicate that
80 : : // id was not resolved; and true if id was resolved.
81 : : GJS_JSAPI_RETURN_CONVENTION
82 : 10632 : bool resolve_impl(JSContext* cx, JS::HandleObject obj, JS::HandleId id,
83 : : bool* resolved) {
84 [ + + ]: 10632 : if (!id.isString()) {
85 : 31 : *resolved = false;
86 : 31 : return true; // not resolved, but no error
87 : : }
88 : :
89 : : // let Object.prototype resolve these
90 : 10601 : const GjsAtoms& atoms = GjsContextPrivate::atoms(cx);
91 [ + + - + : 10601 : if (id == atoms.to_string() || id == atoms.value_of()) {
+ + ]
92 : 15 : *resolved = false;
93 : 15 : return true;
94 : : }
95 : :
96 : 10586 : JS::UniqueChars name;
97 [ - + ]: 10586 : if (!gjs_get_string_id(cx, id, &name))
98 : 0 : return false;
99 [ - + ]: 10586 : if (!name) {
100 : 0 : *resolved = false;
101 : 0 : return true; // not resolved, but no error
102 : : }
103 : :
104 : : GjsAutoBaseInfo info =
105 : 10586 : g_irepository_find_by_name(nullptr, get(), name.get());
106 [ + + ]: 10586 : if (!info) {
107 : 4423 : *resolved = false; // No property defined, but no error either
108 : 4423 : return true;
109 : : }
110 : :
111 : 6163 : gjs_debug(GJS_DEBUG_GNAMESPACE,
112 : : "Found info type %s for '%s' in namespace '%s'",
113 : : gjs_info_type_name(info.type()), info.name(), info.ns());
114 : :
115 : : bool defined;
116 [ - + ]: 6163 : if (!gjs_define_info(cx, obj, info, &defined)) {
117 : 0 : gjs_debug(GJS_DEBUG_GNAMESPACE, "Failed to define info '%s'",
118 : : info.name());
119 : 0 : return false;
120 : : }
121 : :
122 : : // we defined the property in this object?
123 : 6163 : *resolved = defined;
124 : 6163 : return true;
125 : 10586 : }
126 : :
127 : : GJS_JSAPI_RETURN_CONVENTION
128 : 2 : bool new_enumerate_impl(JSContext* cx,
129 : : JS::HandleObject obj [[maybe_unused]],
130 : : JS::MutableHandleIdVector properties,
131 : : bool only_enumerable [[maybe_unused]]) {
132 : 2 : int n = g_irepository_get_n_infos(nullptr, get());
133 [ - + ]: 2 : if (!properties.reserve(properties.length() + n)) {
134 : 0 : JS_ReportOutOfMemory(cx);
135 : 0 : return false;
136 : : }
137 : :
138 [ + + ]: 5018 : for (int k = 0; k < n; k++) {
139 : 5016 : GjsAutoBaseInfo info = g_irepository_get_info(nullptr, get(), k);
140 : 5016 : GIInfoType info_type = g_base_info_get_type(info);
141 [ + + ]: 5016 : if (!type_is_enumerable(info_type))
142 : 10 : continue;
143 : :
144 : 5006 : const char* name = info.name();
145 : :
146 : 5006 : jsid id = gjs_intern_string_to_id(cx, name);
147 [ - + ]: 5006 : if (id.isVoid())
148 : 0 : return false;
149 : 5006 : properties.infallibleAppend(id);
150 [ + + - ]: 5016 : }
151 : :
152 : 2 : return true;
153 : : }
154 : :
155 : 236 : static void finalize_impl(JS::GCContext*, Ns* priv) {
156 : 236 : g_assert(priv && "Finalize called on wrong object");
157 [ + - ]: 236 : delete priv;
158 : 236 : }
159 : :
160 : : // Properties and methods
161 : :
162 : : GJS_JSAPI_RETURN_CONVENTION
163 : 1 : static bool get_name(JSContext* cx, unsigned argc, JS::Value* vp) {
164 [ - + - + ]: 1 : GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, args, this_obj, Ns, priv);
165 : 1 : return gjs_string_from_utf8(cx, priv->get(), args.rval());
166 : 1 : }
167 : :
168 : : GJS_JSAPI_RETURN_CONVENTION
169 : 4 : static bool get_version(JSContext* cx, unsigned argc, JS::Value* vp) {
170 [ - + - + ]: 4 : GJS_CHECK_WRAPPER_PRIV(cx, argc, vp, args, this_obj, Ns, priv);
171 : 4 : const char *version = g_irepository_get_version(nullptr, priv->get());
172 : 4 : return gjs_string_from_utf8(cx, version, args.rval());
173 : 4 : }
174 : :
175 : : static constexpr JSClassOps class_ops = {
176 : : nullptr, // addProperty
177 : : nullptr, // deleteProperty
178 : : nullptr, // enumerate
179 : : &Ns::new_enumerate,
180 : : &Ns::resolve,
181 : : nullptr, // mayResolve
182 : : &Ns::finalize,
183 : : };
184 : :
185 : : // clang-format off
186 : : static constexpr JSPropertySpec proto_props[] = {
187 : : JS_STRING_SYM_PS(toStringTag, "GIRepositoryNamespace", JSPROP_READONLY),
188 : : JS_PSG("__name__", &Ns::get_name, GJS_MODULE_PROP_FLAGS),
189 : : JS_PSG("__version__", &Ns::get_version, GJS_MODULE_PROP_FLAGS & ~JSPROP_ENUMERATE),
190 : : JS_PS_END};
191 : : // clang-format on
192 : :
193 : : static constexpr js::ClassSpec class_spec = {
194 : : nullptr, // createConstructor
195 : : nullptr, // createPrototype
196 : : nullptr, // constructorFunctions
197 : : nullptr, // constructorProperties
198 : : nullptr, // prototypeFunctions
199 : : Ns::proto_props,
200 : : nullptr, // finishInit
201 : : js::ClassSpec::DontDefineConstructor};
202 : :
203 : : static constexpr JSClass klass = {
204 : : "GIRepositoryNamespace",
205 : : JSCLASS_HAS_RESERVED_SLOTS(1) | JSCLASS_FOREGROUND_FINALIZE,
206 : : &Ns::class_ops, &Ns::class_spec};
207 : :
208 : : public:
209 : : GJS_JSAPI_RETURN_CONVENTION
210 : 240 : static JSObject* create(JSContext* cx, const char* ns_name) {
211 : 240 : JS::RootedObject proto(cx, Ns::create_prototype(cx));
212 [ - + ]: 240 : if (!proto)
213 : 0 : return nullptr;
214 : :
215 : : JS::RootedObject ns(cx,
216 : 240 : JS_NewObjectWithGivenProto(cx, &Ns::klass, proto));
217 [ - + ]: 240 : if (!ns)
218 : 0 : return nullptr;
219 : :
220 : 240 : auto* priv = new Ns(ns_name);
221 : 240 : Ns::init_private(ns, priv);
222 : :
223 : : gjs_debug_lifecycle(GJS_DEBUG_GNAMESPACE,
224 : : "ns constructor, obj %p priv %p", ns.get(), priv);
225 : :
226 : 240 : return ns;
227 : 240 : }
228 : : };
229 : :
230 : : JSObject*
231 : 240 : gjs_create_ns(JSContext *context,
232 : : const char *ns_name)
233 : : {
234 : 240 : return Ns::create(context, ns_name);
235 : : }
|