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 <string.h> // for strlen
8 : :
9 : : #if GJS_VERBOSE_ENABLE_GI_USAGE
10 : : # include <string>
11 : : #endif
12 : :
13 : : #include <girepository.h>
14 : : #include <glib-object.h>
15 : : #include <glib.h>
16 : :
17 : : #include <js/CallAndConstruct.h> // for JS_CallFunctionValue
18 : : #include <js/Class.h>
19 : : #include <js/ComparisonOperators.h>
20 : : #include <js/Exception.h>
21 : : #include <js/GlobalObject.h> // for CurrentGlobalOrNull
22 : : #include <js/Id.h> // for PropertyKey
23 : : #include <js/Object.h> // for GetClass
24 : : #include <js/PropertyAndElement.h>
25 : : #include <js/PropertyDescriptor.h> // for JSPROP_PERMANENT, JSPROP_RESOLVING
26 : : #include <js/RootingAPI.h>
27 : : #include <js/String.h>
28 : : #include <js/TypeDecls.h>
29 : : #include <js/Utility.h> // for UniqueChars
30 : : #include <js/Value.h>
31 : : #include <js/ValueArray.h>
32 : : #include <js/Warnings.h>
33 : : #include <jsapi.h> // for JS_NewPlainObject, JS_NewObject
34 : :
35 : : #include "gi/arg.h"
36 : : #include "gi/boxed.h"
37 : : #include "gi/enumeration.h"
38 : : #include "gi/function.h"
39 : : #include "gi/fundamental.h"
40 : : #include "gi/gerror.h"
41 : : #include "gi/info.h"
42 : : #include "gi/interface.h"
43 : : #include "gi/ns.h"
44 : : #include "gi/object.h"
45 : : #include "gi/param.h"
46 : : #include "gi/repo.h"
47 : : #include "gi/union.h"
48 : : #include "gjs/atoms.h"
49 : : #include "gjs/auto.h"
50 : : #include "gjs/context-private.h"
51 : : #include "gjs/gerror-result.h"
52 : : #include "gjs/global.h"
53 : : #include "gjs/jsapi-util.h"
54 : : #include "gjs/macros.h"
55 : : #include "gjs/module.h"
56 : : #include "util/log.h"
57 : :
58 : : GJS_JSAPI_RETURN_CONVENTION
59 : : static bool lookup_override_function(JSContext *, JS::HandleId,
60 : : JS::MutableHandleValue);
61 : :
62 : : GJS_JSAPI_RETURN_CONVENTION
63 : 263 : static bool get_version_for_ns(JSContext* context, JS::HandleObject repo_obj,
64 : : JS::HandleId ns_id, JS::UniqueChars* version) {
65 : 263 : JS::RootedObject versions(context);
66 : : bool found;
67 : 263 : const GjsAtoms& atoms = GjsContextPrivate::atoms(context);
68 : :
69 [ - + ]: 263 : if (!gjs_object_require_property(context, repo_obj, "GI repository object",
70 : : atoms.versions(), &versions))
71 : 0 : return false;
72 : :
73 [ - + ]: 263 : if (!JS_AlreadyHasOwnPropertyById(context, versions, ns_id, &found))
74 : 0 : return false;
75 : :
76 [ + + ]: 263 : if (!found)
77 : 96 : return true;
78 : :
79 : 167 : return gjs_object_require_property(context, versions, NULL, ns_id, version);
80 : 263 : }
81 : :
82 : 261 : static void strlist_free(GList* l) { g_list_free_full(l, g_free); }
83 : :
84 : : GJS_JSAPI_RETURN_CONVENTION
85 : 263 : static bool resolve_namespace_object(JSContext* context,
86 : : JS::HandleObject repo_obj,
87 : : JS::HandleId ns_id) {
88 : 263 : JS::UniqueChars version;
89 [ - + ]: 263 : if (!get_version_for_ns(context, repo_obj, ns_id, &version))
90 : 0 : return false;
91 : :
92 : 263 : JS::UniqueChars ns_name;
93 [ - + ]: 263 : if (!gjs_get_string_id(context, ns_id, &ns_name))
94 : 0 : return false;
95 [ - + ]: 263 : if (!ns_name) {
96 : 0 : gjs_throw(context, "Requiring invalid namespace on imports.gi");
97 : 0 : return false;
98 : : }
99 : :
100 : 261 : Gjs::AutoPointer<GList, GList, strlist_free> versions{
101 : 263 : g_irepository_enumerate_versions(nullptr, ns_name.get())};
102 : 263 : unsigned nversions = g_list_length(versions);
103 [ + + - + ]: 102 : if (nversions > 1 && !version &&
104 [ + + - + ]: 365 : !g_irepository_is_registered(nullptr, ns_name.get(), nullptr) &&
105 [ # # ]: 0 : !JS::WarnUTF8(context,
106 : : "Requiring %s but it has %u versions available; use "
107 : : "imports.gi.versions to pick one",
108 : : ns_name.get(), nversions))
109 : 0 : return false;
110 : :
111 : 263 : Gjs::AutoError error;
112 : : // If resolving Gio, load the platform-specific typelib first, so that
113 : : // GioUnix/GioWin32 GTypes get looked up in there with higher priority,
114 : : // instead of in Gio.
115 : : #if GLIB_CHECK_VERSION(2, 79, 2) && (defined(G_OS_UNIX) || defined(G_OS_WIN32))
116 [ + + ]: 263 : if (strcmp(ns_name.get(), "Gio") == 0) {
117 : : # ifdef G_OS_UNIX
118 : 25 : const char* platform = "Unix";
119 : : # else // G_OS_WIN32
120 : : const char* platform = "Win32";
121 : : # endif // G_OS_UNIX/G_OS_WIN32
122 : : Gjs::AutoChar platform_specific{
123 : 25 : g_strconcat(ns_name.get(), platform, nullptr)};
124 [ - + ]: 25 : if (!g_irepository_require(nullptr, platform_specific, version.get(),
125 : : GIRepositoryLoadFlags(0), &error)) {
126 : 0 : gjs_throw(context, "Failed to require %s %s: %s",
127 : 0 : platform_specific.get(), version.get(), error->message);
128 : 0 : return false;
129 : : }
130 [ + - ]: 25 : }
131 : : #endif // GLib >= 2.79.2
132 : :
133 : 263 : g_irepository_require(nullptr, ns_name.get(), version.get(),
134 : : GIRepositoryLoadFlags(0), &error);
135 [ + + ]: 263 : if (error) {
136 [ + + ]: 5 : gjs_throw(context, "Requiring %s, version %s: %s", ns_name.get(),
137 : 5 : version ? version.get() : "none", error->message);
138 : 3 : return false;
139 : : }
140 : :
141 : : /* Defines a property on "obj" (the javascript repo object)
142 : : * with the given namespace name, pointing to that namespace
143 : : * in the repo.
144 : : */
145 : : JS::RootedObject gi_namespace(context,
146 : 260 : gjs_create_ns(context, ns_name.get()));
147 : :
148 : 260 : JS::RootedValue override(context);
149 [ + + ]: 513 : if (!lookup_override_function(context, ns_id, &override) ||
150 : : // Define the property early, to avoid reentrancy issues if the override
151 : : // module looks for namespaces that import this
152 [ - + + + ]: 513 : !JS_DefinePropertyById(context, repo_obj, ns_id, gi_namespace,
153 : : GJS_MODULE_PROP_FLAGS))
154 : 7 : return false;
155 : :
156 : 253 : JS::RootedValue result(context);
157 [ + + ]: 411 : if (!override.isUndefined() &&
158 [ + + ]: 316 : !JS_CallFunctionValue (context, gi_namespace, /* thisp */
159 : : override, /* callee */
160 [ + + ]: 411 : JS::HandleValueArray::empty(), &result))
161 : 1 : return false;
162 : :
163 : 252 : gjs_debug(GJS_DEBUG_GNAMESPACE,
164 : : "Defined namespace '%s' %p in GIRepository %p", ns_name.get(),
165 : 252 : gi_namespace.get(), repo_obj.get());
166 : :
167 : 252 : GjsContextPrivate* gjs = GjsContextPrivate::from_cx(context);
168 : 252 : gjs->schedule_gc_if_needed();
169 : 252 : return true;
170 : 263 : }
171 : :
172 : : /*
173 : : * The *resolved out parameter, on success, should be false to indicate that id
174 : : * was not resolved; and true if id was resolved.
175 : : */
176 : : GJS_JSAPI_RETURN_CONVENTION
177 : : static bool
178 : 263 : repo_resolve(JSContext *context,
179 : : JS::HandleObject obj,
180 : : JS::HandleId id,
181 : : bool *resolved)
182 : : {
183 [ - + ]: 263 : if (!id.isString()) {
184 : 0 : *resolved = false;
185 : 0 : return true; /* not resolved, but no error */
186 : : }
187 : :
188 : : /* let Object.prototype resolve these */
189 : 263 : const GjsAtoms& atoms = GjsContextPrivate::atoms(context);
190 [ + - - + : 263 : if (id == atoms.to_string() || id == atoms.value_of()) {
- + ]
191 : 0 : *resolved = false;
192 : 0 : return true;
193 : : }
194 : :
195 : : gjs_debug_jsprop(GJS_DEBUG_GREPO, "Resolve prop '%s' hook, obj %s",
196 : : gjs_debug_id(id).c_str(), gjs_debug_object(obj).c_str());
197 : :
198 [ + + ]: 263 : if (!resolve_namespace_object(context, obj, id))
199 : 11 : return false;
200 : :
201 : 252 : *resolved = true;
202 : 252 : return true;
203 : : }
204 : :
205 : : static const struct JSClassOps gjs_repo_class_ops = {
206 : : nullptr, // addProperty
207 : : nullptr, // deleteProperty
208 : : nullptr, // enumerate
209 : : nullptr, // newEnumerate
210 : : repo_resolve,
211 : : };
212 : :
213 : : struct JSClass gjs_repo_class = {
214 : : "GIRepository",
215 : : 0,
216 : : &gjs_repo_class_ops,
217 : : };
218 : :
219 : : GJS_JSAPI_RETURN_CONVENTION
220 : : static JSObject*
221 : 87 : repo_new(JSContext *context)
222 : : {
223 : 87 : JS::RootedObject repo(context, JS_NewObject(context, &gjs_repo_class));
224 [ - + ]: 87 : if (repo == nullptr)
225 : 0 : return nullptr;
226 : :
227 : : gjs_debug_lifecycle(GJS_DEBUG_GREPO, "repo constructor, obj %p",
228 : : repo.get());
229 : :
230 : 87 : const GjsAtoms& atoms = GjsContextPrivate::atoms(context);
231 : 87 : JS::RootedObject versions(context, JS_NewPlainObject(context));
232 [ - + ]: 87 : if (!JS_DefinePropertyById(context, repo, atoms.versions(), versions,
233 : : JSPROP_PERMANENT | JSPROP_RESOLVING))
234 : 0 : return nullptr;
235 : :
236 : : /* GLib/GObject/Gio are fixed at 2.0, since we depend on them
237 : : * internally.
238 : : */
239 : 87 : JS::RootedString two_point_oh(context, JS_NewStringCopyZ(context, "2.0"));
240 : 87 : if (!JS_DefinePropertyById(context, versions, atoms.glib(), two_point_oh,
241 : 87 : JSPROP_PERMANENT) ||
242 [ + - ]: 87 : !JS_DefinePropertyById(context, versions, atoms.gobject(), two_point_oh,
243 [ + - ]: 174 : JSPROP_PERMANENT) ||
244 [ - + - + ]: 174 : !JS_DefinePropertyById(context, versions, atoms.gio(), two_point_oh,
245 : : JSPROP_PERMANENT))
246 : 0 : return nullptr;
247 : :
248 : : #if GLIB_CHECK_VERSION(2, 79, 2)
249 : : # if defined(G_OS_UNIX)
250 : 87 : if (!JS_DefineProperty(context, versions, "GLibUnix", two_point_oh,
251 [ + - ]: 174 : JSPROP_PERMANENT) ||
252 [ - + - + ]: 174 : !JS_DefineProperty(context, versions, "GioUnix", two_point_oh,
253 : : JSPROP_PERMANENT))
254 : 0 : return nullptr;
255 : : # elif defined(G_OS_WIN32)
256 : : if (!JS_DefineProperty(context, versions, "GLibWin32", two_point_oh,
257 : : JSPROP_PERMANENT) ||
258 : : !JS_DefineProperty(context, versions, "GioWin32", two_point_oh,
259 : : JSPROP_PERMANENT))
260 : : return nullptr;
261 : : # endif // G_OS_UNIX/G_OS_WIN32
262 : : #endif // GLib >= 2.79.2
263 : :
264 : 87 : JS::RootedObject private_ns(context, JS_NewPlainObject(context));
265 [ - + ]: 87 : if (!JS_DefinePropertyById(context, repo, atoms.private_ns_marker(),
266 : : private_ns, JSPROP_PERMANENT | JSPROP_RESOLVING))
267 : 0 : return nullptr;
268 : :
269 : 87 : return repo;
270 : 87 : }
271 : :
272 : : bool
273 : 87 : gjs_define_repo(JSContext *cx,
274 : : JS::MutableHandleObject repo)
275 : : {
276 : 87 : repo.set(repo_new(cx));
277 : 87 : return true;
278 : : }
279 : :
280 : : GJS_JSAPI_RETURN_CONVENTION
281 : 4717 : static bool gjs_value_from_constant_info(JSContext* cx, GIConstantInfo* info,
282 : : JS::MutableHandleValue value) {
283 : : GIArgument garg;
284 : 4717 : g_constant_info_get_value(info, &garg);
285 : :
286 : 4717 : GI::AutoTypeInfo type_info{g_constant_info_get_type(info)};
287 : :
288 : 4717 : bool ok = gjs_value_from_gi_argument(cx, value, type_info, &garg, true);
289 : :
290 : 4717 : g_constant_info_free_value(info, &garg);
291 : 9434 : return ok;
292 : 4717 : }
293 : :
294 : : GJS_JSAPI_RETURN_CONVENTION
295 : : static bool
296 : 4717 : gjs_define_constant(JSContext *context,
297 : : JS::HandleObject in_object,
298 : : GIConstantInfo *info)
299 : : {
300 : 4717 : JS::RootedValue value(context);
301 : : const char *name;
302 : :
303 [ - + ]: 4717 : if (!gjs_value_from_constant_info(context, info, &value))
304 : 0 : return false;
305 : :
306 : 4717 : name = g_base_info_get_name((GIBaseInfo*) info);
307 : :
308 : 9434 : return JS_DefineProperty(context, in_object, name, value,
309 : 4717 : GJS_MODULE_PROP_FLAGS);
310 : 4717 : }
311 : :
312 : : #if GJS_VERBOSE_ENABLE_GI_USAGE
313 : : void
314 : : _gjs_log_info_usage(GIBaseInfo *info)
315 : : {
316 : : #define DIRECTION_STRING(d) ( ((d) == GI_DIRECTION_IN) ? "IN" : ((d) == GI_DIRECTION_OUT) ? "OUT" : "INOUT" )
317 : : #define TRANSFER_STRING(t) ( ((t) == GI_TRANSFER_NOTHING) ? "NOTHING" : ((t) == GI_TRANSFER_CONTAINER) ? "CONTAINER" : "EVERYTHING" )
318 : :
319 : : {
320 : : char *details;
321 : : GIBaseInfo *container;
322 : :
323 : : if (GI_IS_FUNCTION_INFO(info)) {
324 : : std::string args("{ ");
325 : : int n_args;
326 : : int i;
327 : : GITransfer retval_transfer;
328 : :
329 : : n_args = g_callable_info_get_n_args((GICallableInfo*) info);
330 : : for (i = 0; i < n_args; ++i) {
331 : : GIArgInfo *arg;
332 : : GIDirection direction;
333 : : GITransfer transfer;
334 : :
335 : : arg = g_callable_info_get_arg((GICallableInfo*)info, i);
336 : : direction = g_arg_info_get_direction(arg);
337 : : transfer = g_arg_info_get_ownership_transfer(arg);
338 : :
339 : : if (i > 0)
340 : : args += ", ";
341 : :
342 : : args += std::string("{ GI_DIRECTION_") +
343 : : DIRECTION_STRING(direction) + ", GI_TRANSFER_" +
344 : : TRANSFER_STRING(transfer) + " }";
345 : :
346 : : g_base_info_unref((GIBaseInfo*) arg);
347 : : }
348 : :
349 : : args += " }";
350 : :
351 : : retval_transfer = g_callable_info_get_caller_owns((GICallableInfo*) info);
352 : :
353 : : details = g_strdup_printf(
354 : : ".details = { .func = { .retval_transfer = GI_TRANSFER_%s, "
355 : : ".n_args = %d, .args = %s } }",
356 : : TRANSFER_STRING(retval_transfer), n_args, args.c_str());
357 : : } else {
358 : : details = g_strdup_printf(".details = { .nothing = {} }");
359 : : }
360 : :
361 : : container = g_base_info_get_container(info);
362 : :
363 : : gjs_debug_gi_usage("{ GIInfoType %s, \"%s\", \"%s\", \"%s\", %s },",
364 : : g_info_type_to_string(g_base_info_get_type(info)),
365 : : g_base_info_get_namespace(info),
366 : : container ? g_base_info_get_name(container) : "",
367 : : g_base_info_get_name(info), details);
368 : : g_free(details);
369 : : }
370 : : }
371 : : #endif /* GJS_VERBOSE_ENABLE_GI_USAGE */
372 : :
373 : 9374 : bool gjs_define_info(JSContext* cx, JS::HandleObject in_object,
374 : : GIBaseInfo* info, bool* defined) {
375 : : #if GJS_VERBOSE_ENABLE_GI_USAGE
376 : : _gjs_log_info_usage(info);
377 : : #endif
378 : :
379 : 9374 : *defined = true;
380 : :
381 [ + + ]: 9374 : if (GI_IS_FUNCTION_INFO(info))
382 : 2706 : return gjs_define_function(cx, in_object, 0, info);
383 : :
384 [ + + ]: 6668 : if (GI_IS_OBJECT_INFO(info)) {
385 : 614 : GType gtype = g_registered_type_info_get_g_type(info);
386 : :
387 [ + + - + : 614 : if (g_type_is_a(gtype, G_TYPE_PARAM))
+ + ]
388 : 58 : return gjs_define_param_class(cx, in_object);
389 : :
390 [ + + + + : 556 : if (g_type_is_a(gtype, G_TYPE_OBJECT)) {
+ + ]
391 : 546 : JS::RootedObject ignored1{cx}, ignored2{cx};
392 : 546 : return ObjectPrototype::define_class(
393 : 546 : cx, in_object, info, gtype, nullptr, 0, &ignored1, &ignored2);
394 : 546 : }
395 : :
396 [ + - ]: 10 : if (G_TYPE_IS_INSTANTIATABLE(gtype)) {
397 : 10 : JS::RootedObject ignored{cx};
398 : 10 : return FundamentalPrototype::define_class(cx, in_object, info,
399 : 10 : &ignored);
400 : 10 : }
401 : :
402 : 0 : gjs_throw(cx, "Unsupported type %s, deriving from fundamental %s",
403 : : g_type_name(gtype), g_type_name(g_type_fundamental(gtype)));
404 : 0 : return false;
405 : : }
406 : :
407 : : // We don't want GType structures in the namespace, we expose their fields
408 : : // as vfuncs and their methods as static methods
409 [ + + + + : 6054 : if (GI_IS_STRUCT_INFO(info) && g_struct_info_is_gtype_struct(info)) {
+ + ]
410 : 10 : *defined = false;
411 : 10 : return true;
412 : : }
413 : :
414 [ + + + + : 11267 : if (GI_IS_STRUCT_INFO(info) ||
+ + ]
415 : 5223 : g_base_info_get_type(info) == GI_INFO_TYPE_BOXED)
416 : 879 : return BoxedPrototype::define_class(cx, in_object, info);
417 : :
418 [ + + ]: 5165 : if (GI_IS_UNION_INFO(info))
419 : 5 : return UnionPrototype::define_class(cx, in_object, (GIUnionInfo*)info);
420 : :
421 [ + + + + : 5160 : if (GI_IS_ENUM_INFO(info)) {
+ + ]
422 [ + + + + : 369 : if (g_base_info_get_type(info) != GI_INFO_TYPE_FLAGS &&
+ + ]
423 : 139 : g_enum_info_get_error_domain(info)) {
424 : : /* define as GError subclass */
425 : 13 : return ErrorPrototype::define_class(cx, in_object, info);
426 : : }
427 : :
428 : 217 : return gjs_define_enumeration(cx, in_object, info);
429 : : }
430 : :
431 [ + + ]: 4930 : if (GI_IS_CONSTANT_INFO(info))
432 : 4717 : return gjs_define_constant(cx, in_object, info);
433 : :
434 [ + - ]: 213 : if (GI_IS_INTERFACE_INFO(info)) {
435 : 213 : JS::RootedObject ignored1{cx}, ignored2{cx};
436 : 213 : return InterfacePrototype::create_class(
437 : : cx, in_object, info, g_registered_type_info_get_g_type(info),
438 : 213 : &ignored1, &ignored2);
439 : 213 : }
440 : :
441 : 0 : gjs_throw(cx, "API of type %s not implemented, cannot define %s.%s",
442 : : g_info_type_to_string(g_base_info_get_type(info)),
443 : : g_base_info_get_namespace(info), g_base_info_get_name(info));
444 : 0 : return false;
445 : : }
446 : :
447 : : /* Get the "unknown namespace", which should be used for unnamespaced types */
448 : : JSObject*
449 : 319 : gjs_lookup_private_namespace(JSContext *context)
450 : : {
451 : 319 : const GjsAtoms& atoms = GjsContextPrivate::atoms(context);
452 : 319 : return gjs_lookup_namespace_object_by_name(context,
453 : 319 : atoms.private_ns_marker());
454 : : }
455 : :
456 : : /* Get the namespace object that the GIBaseInfo should be inside */
457 : : JSObject*
458 : 22541 : gjs_lookup_namespace_object(JSContext *context,
459 : : GIBaseInfo *info)
460 : : {
461 : : const char *ns;
462 : :
463 : 22541 : ns = g_base_info_get_namespace(info);
464 [ - + ]: 22541 : if (ns == NULL) {
465 : 0 : gjs_throw(context, "%s '%s' does not have a namespace",
466 : : g_info_type_to_string(g_base_info_get_type(info)),
467 : : g_base_info_get_name(info));
468 : :
469 : 0 : return NULL;
470 : : }
471 : :
472 : 22541 : JS::RootedId ns_name(context, gjs_intern_string_to_id(context, ns));
473 [ - + ]: 22541 : if (ns_name.isVoid())
474 : 0 : return nullptr;
475 : 22541 : return gjs_lookup_namespace_object_by_name(context, ns_name);
476 : 22541 : }
477 : :
478 : : /* Check if an exception's 'name' property is equal to ImportError. Ignores
479 : : * all errors that might arise. */
480 : 97 : [[nodiscard]] static bool is_import_error(JSContext* cx,
481 : : JS::HandleValue thrown_value) {
482 [ + + ]: 97 : if (!thrown_value.isObject())
483 : 1 : return false;
484 : :
485 : 96 : JS::AutoSaveExceptionState saved_exc(cx);
486 : 96 : JS::RootedObject exc(cx, &thrown_value.toObject());
487 : 96 : JS::RootedValue exc_name(cx);
488 : 96 : const GjsAtoms& atoms = GjsContextPrivate::atoms(cx);
489 : : bool eq;
490 : : bool retval =
491 [ + - ]: 192 : JS_GetPropertyById(cx, exc, atoms.name(), &exc_name) &&
492 [ + - + + ]: 192 : JS_StringEqualsLiteral(cx, exc_name.toString(), "ImportError", &eq) &&
493 : 96 : eq;
494 : :
495 : 96 : saved_exc.restore();
496 : 96 : return retval;
497 : 96 : }
498 : :
499 : : GJS_JSAPI_RETURN_CONVENTION
500 : : static bool
501 : 260 : lookup_override_function(JSContext *cx,
502 : : JS::HandleId ns_name,
503 : : JS::MutableHandleValue function)
504 : : {
505 : 260 : JS::AutoSaveExceptionState saved_exc(cx);
506 : :
507 : 260 : JS::RootedObject global{cx, JS::CurrentGlobalOrNull(cx)};
508 : : JS::RootedValue importer(
509 : 260 : cx, gjs_get_global_slot(global, GjsGlobalSlot::IMPORTS));
510 : 260 : g_assert(importer.isObject());
511 : :
512 : 260 : JS::RootedObject overridespkg(cx), module(cx);
513 : 260 : JS::RootedObject importer_obj(cx, &importer.toObject());
514 : 260 : const GjsAtoms& atoms = GjsContextPrivate::atoms(cx);
515 [ - + ]: 260 : if (!gjs_object_require_property(cx, importer_obj, "importer",
516 : : atoms.overrides(), &overridespkg))
517 : 0 : return false;
518 : :
519 [ + + ]: 260 : if (!gjs_object_require_property(cx, overridespkg,
520 : : "GI repository object", ns_name,
521 : : &module)) {
522 : 97 : JS::RootedValue exc(cx);
523 : 97 : JS_GetPendingException(cx, &exc);
524 : :
525 : : /* If the exception was an ImportError (i.e., module not found) then
526 : : * we simply didn't have an override, don't throw an exception */
527 [ + + ]: 97 : if (is_import_error(cx, exc)) {
528 : 95 : saved_exc.restore();
529 : 95 : return true;
530 : : }
531 : :
532 : 2 : return false;
533 : 97 : }
534 : :
535 : : // If the override module is present, it must have a callable _init(). An
536 : : // override module without _init() is probably unintentional. (function
537 : : // being undefined means there was no override module.)
538 : 163 : if (!gjs_object_require_property(cx, module, "override module",
539 : 161 : atoms.init(), function) ||
540 [ + + + + : 163 : !function.isObject() || !JS::IsCallable(&function.toObject())) {
+ + + + ]
541 : 5 : gjs_throw(cx, "Unexpected value for _init in overrides module");
542 : 5 : return false;
543 : : }
544 : 158 : return true;
545 : 260 : }
546 : :
547 : : GJS_JSAPI_RETURN_CONVENTION
548 : 23158 : static JSObject* lookup_namespace(JSContext* cx, JSObject* global,
549 : : JS::HandleId ns_name) {
550 : 23158 : JS::RootedObject native_registry(cx, gjs_get_native_registry(global));
551 : 23158 : auto priv = GjsContextPrivate::from_cx(cx);
552 : 23158 : const GjsAtoms& atoms = priv->atoms();
553 : 23158 : JS::RootedObject gi(cx);
554 : :
555 [ - + ]: 23158 : if (!gjs_global_registry_get(cx, native_registry, atoms.gi(), &gi))
556 : 0 : return nullptr;
557 : :
558 [ - + ]: 23158 : if (!gi) {
559 : 0 : gjs_throw(cx, "No gi property in native registry");
560 : 0 : return nullptr;
561 : : }
562 : :
563 : 23158 : JS::RootedObject retval(cx);
564 [ - + ]: 23158 : if (!gjs_object_require_property(cx, gi, "GI repository object", ns_name,
565 : : &retval))
566 : 0 : return NULL;
567 : :
568 : 23158 : return retval;
569 : 23158 : }
570 : :
571 : 23158 : JSObject* gjs_lookup_namespace_object_by_name(JSContext* cx,
572 : : JS::HandleId ns_name) {
573 : 23158 : JS::RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
574 : :
575 : 23158 : g_assert(gjs_global_get_type(global) == GjsGlobalType::DEFAULT);
576 : 23158 : return lookup_namespace(cx, global, ns_name);
577 : 23158 : }
578 : :
579 : : char*
580 : 4517 : gjs_hyphen_from_camel(const char *camel_name)
581 : : {
582 : : GString *s;
583 : : const char *p;
584 : :
585 : : /* four hyphens should be reasonable guess */
586 : 4517 : s = g_string_sized_new(strlen(camel_name) + 4 + 1);
587 : :
588 [ + + ]: 52284 : for (p = camel_name; *p; ++p) {
589 [ + + ]: 47767 : if (g_ascii_isupper(*p)) {
590 : : g_string_append_c(s, '-');
591 [ + - ]: 977 : g_string_append_c(s, g_ascii_tolower(*p));
592 : : } else {
593 [ + - ]: 46790 : g_string_append_c(s, *p);
594 : : }
595 : : }
596 : :
597 : 4517 : return g_string_free(s, false);
598 : : }
599 : :
600 : : JSObject *
601 : 19090 : gjs_lookup_generic_constructor(JSContext *context,
602 : : GIBaseInfo *info)
603 : : {
604 : : JS::RootedObject in_object{context,
605 : 19090 : gjs_lookup_namespace_object(context, info)};
606 : 19090 : const char* constructor_name = g_base_info_get_name(info);
607 : :
608 [ - + ]: 19090 : if (G_UNLIKELY (!in_object))
609 : 0 : return NULL;
610 : :
611 : 19090 : JS::RootedValue value(context);
612 [ - + ]: 19090 : if (!JS_GetProperty(context, in_object, constructor_name, &value))
613 : 0 : return NULL;
614 : :
615 [ - + ]: 19090 : if (G_UNLIKELY(!value.isObject())) {
616 : 0 : gjs_throw(context,
617 : : "Constructor of %s.%s was the wrong type, expected an object",
618 : : g_base_info_get_namespace(info), constructor_name);
619 : 0 : return NULL;
620 : : }
621 : :
622 : 19090 : return &value.toObject();
623 : 19090 : }
624 : :
625 : : JSObject *
626 : 19090 : gjs_lookup_generic_prototype(JSContext *context,
627 : : GIBaseInfo *info)
628 : : {
629 : : JS::RootedObject constructor(context,
630 : 19090 : gjs_lookup_generic_constructor(context, info));
631 [ - + ]: 19090 : if (G_UNLIKELY(!constructor))
632 : 0 : return NULL;
633 : :
634 : 19090 : const GjsAtoms& atoms = GjsContextPrivate::atoms(context);
635 : 19090 : JS::RootedValue value(context);
636 [ - + ]: 19090 : if (!JS_GetPropertyById(context, constructor, atoms.prototype(), &value))
637 : 0 : return NULL;
638 : :
639 [ - + ]: 19090 : if (G_UNLIKELY(!value.isObject())) {
640 : 0 : gjs_throw(context,
641 : : "Prototype of %s.%s was the wrong type, expected an object",
642 : : g_base_info_get_namespace(info), g_base_info_get_name(info));
643 : 0 : return NULL;
644 : : }
645 : :
646 : 19090 : return &value.toObject();
647 : 19090 : }
648 : :
649 : 19074 : JSObject* gjs_new_object_with_generic_prototype(JSContext* cx,
650 : : GIBaseInfo* info) {
651 : 19074 : JS::RootedObject proto(cx, gjs_lookup_generic_prototype(cx, info));
652 [ - + ]: 19074 : if (!proto)
653 : 0 : return nullptr;
654 : :
655 : 19074 : return JS_NewObjectWithGivenProto(cx, JS::GetClass(proto), proto);
656 : 19074 : }
657 : :
658 : : // Handle the case where g_irepository_find_by_gtype() returns a type in Gio
659 : : // that should be in GioUnix or GioWin32. This may be an interface, class, or
660 : : // boxed. This function only needs to be called if you are going to do something
661 : : // with the GIBaseInfo that involves handing a JS object to the user. Otherwise,
662 : : // use g_irepository_find_by_gtype() directly.
663 : 2084 : GIBaseInfo* gjs_lookup_gtype(GIRepository* repo, GType gtype) {
664 : 2084 : GI::AutoBaseInfo retval{g_irepository_find_by_gtype(repo, gtype)};
665 [ + + ]: 2084 : if (!retval)
666 : 183 : return nullptr;
667 : :
668 : : #if GLIB_CHECK_VERSION(2, 79, 2) && (defined(G_OS_UNIX) || defined(G_OS_WIN32))
669 : : # ifdef G_OS_UNIX
670 : : static const char* c_prefix = "GUnix";
671 : : static const char* new_ns = "GioUnix";
672 : : # else // G_OS_WIN32
673 : : static const char* c_prefix = "GWin32";
674 : : static const char* new_ns = "GioWin32";
675 : : # endif
676 : :
677 : 1901 : const char* ns = g_base_info_get_namespace(retval);
678 [ + + ]: 1901 : if (strcmp(ns, "Gio") != 0)
679 : 1438 : return retval.release();
680 : :
681 : 463 : const char* gtype_name = g_type_name(gtype);
682 [ + + ]: 463 : if (!g_str_has_prefix(gtype_name, c_prefix))
683 : 446 : return retval.release();
684 : :
685 : 17 : const char* new_name = gtype_name + strlen(c_prefix);
686 : 17 : GIBaseInfo* new_info = g_irepository_find_by_name(repo, new_ns, new_name);
687 [ - + ]: 17 : if (new_info)
688 : 0 : return new_info;
689 : : #endif // GLib >= 2.79.2
690 : :
691 : 17 : return retval.release();
692 : 2084 : }
|