/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2008 litl, LLC // SPDX-FileCopyrightText: 2012 Red Hat, Inc. #include #include #include #include // for SystemAllocPolicy #include #include #include // for WeakCache #include // for JSPROP_PERMANENT #include #include #include #include // for JS_GetPropertyById, JS_AtomizeString #include #include "gi/cwrapper.h" #include "gi/gtype.h" #include "gjs/atoms.h" #include "gjs/context-private.h" #include "gjs/global.h" #include "gjs/jsapi-util.h" #include "util/log.h" /* * GTypeObj: * * Wrapper object used to represent a GType in JavaScript. * In C, GTypes are just a pointer-sized integer, but in JS they have a 'name' * property and a toString() method. * The integer is stuffed into CWrapper's pointer slot. */ class GTypeObj : public CWrapper { friend CWrapperPointerOps; friend CWrapper; static constexpr auto PROTOTYPE_SLOT = GjsGlobalSlot::PROTOTYPE_gtype; static constexpr GjsDebugTopic DEBUG_TOPIC = GJS_DEBUG_GTYPE; // JSClass operations // No private data is allocated, it's stuffed directly in the private field // of JSObject, so nothing to free static void finalize_impl(JSFreeOp*, void*) {} // Properties GJS_JSAPI_RETURN_CONVENTION static bool get_name(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_GET_THIS(cx, argc, vp, args, obj); GType gtype = value(cx, obj, &args); if (gtype == 0) return false; return gjs_string_from_utf8(cx, g_type_name(gtype), args.rval()); } // Methods GJS_JSAPI_RETURN_CONVENTION static bool to_string(JSContext* cx, unsigned argc, JS::Value* vp) { GJS_GET_THIS(cx, argc, vp, rec, obj); GType gtype = value(cx, obj, &rec); if (gtype == 0) return false; GjsAutoChar strval = g_strdup_printf("[object GType for '%s']", g_type_name(gtype)); return gjs_string_from_utf8(cx, strval, rec.rval()); } // clang-format off static constexpr JSPropertySpec proto_props[] = { JS_PSG("name", >ypeObj::get_name, JSPROP_PERMANENT), JS_STRING_SYM_PS(toStringTag, "GIRepositoryGType", JSPROP_READONLY), JS_PS_END}; static constexpr JSFunctionSpec proto_funcs[] = { JS_FN("toString", >ypeObj::to_string, 0, 0), JS_FS_END}; // clang-format on static constexpr js::ClassSpec class_spec = { nullptr, // createConstructor nullptr, // createPrototype nullptr, // constructorFunctions nullptr, // constructorProperties GTypeObj::proto_funcs, GTypeObj::proto_props, nullptr, // finishInit js::ClassSpec::DontDefineConstructor}; static constexpr JSClass klass = { "GIRepositoryGType", JSCLASS_HAS_PRIVATE | JSCLASS_FOREGROUND_FINALIZE, >ypeObj::class_ops, >ypeObj::class_spec}; GJS_JSAPI_RETURN_CONVENTION static GType value(JSContext* cx, JS::HandleObject obj, JS::CallArgs* args) { void* data; if (!for_js_typecheck(cx, obj, &data, args)) return G_TYPE_NONE; return GPOINTER_TO_SIZE(data); } GJS_JSAPI_RETURN_CONVENTION static GType value(JSContext* cx, JS::HandleObject obj) { return GPOINTER_TO_SIZE(for_js(cx, obj)); } GJS_JSAPI_RETURN_CONVENTION static bool actual_gtype_recurse(JSContext* cx, const GjsAtoms& atoms, JS::HandleObject object, GType* gtype_out, int recurse) { GType gtype = value(cx, object); if (gtype > 0) { *gtype_out = gtype; return true; } JS::RootedValue v_gtype(cx); // OK, we don't have a GType wrapper object -- grab the "$gtype" // property on that and hope it's a GType wrapper object if (!JS_GetPropertyById(cx, object, atoms.gtype(), &v_gtype)) return false; if (!v_gtype.isObject()) { // OK, so we're not a class. But maybe we're an instance. Check for // "constructor" and recurse on that. if (!JS_GetPropertyById(cx, object, atoms.constructor(), &v_gtype)) return false; } if (recurse > 0 && v_gtype.isObject()) { JS::RootedObject gtype_obj(cx, &v_gtype.toObject()); return actual_gtype_recurse(cx, atoms, gtype_obj, gtype_out, recurse - 1); } *gtype_out = G_TYPE_INVALID; return true; } public: GJS_JSAPI_RETURN_CONVENTION static JSObject* create(JSContext* cx, GType gtype) { g_assert(gtype != 0 && "Attempted to create wrapper object for invalid GType"); GjsContextPrivate* gjs = GjsContextPrivate::from_cx(cx); // We cannot use gtype_table().lookupForAdd() here, because in between // the lookup and the add, GCs may take place and mutate the hash table. // A GC may only remove an element, not add one, so it's still safe to // do this without locking. auto p = gjs->gtype_table().lookup(gtype); if (p.found()) return p->value(); JS::RootedObject proto(cx, GTypeObj::create_prototype(cx)); if (!proto) return nullptr; JS::RootedObject gtype_wrapper( cx, JS_NewObjectWithGivenProto(cx, >ypeObj::klass, proto)); if (!gtype_wrapper) return nullptr; JS_SetPrivate(gtype_wrapper, GSIZE_TO_POINTER(gtype)); gjs->gtype_table().put(gtype, gtype_wrapper); return gtype_wrapper; } GJS_JSAPI_RETURN_CONVENTION static bool actual_gtype(JSContext* cx, JS::HandleObject object, GType* gtype_out) { g_assert(gtype_out && "Missing return location"); // 2 means: recurse at most three times (including this call). // The levels are calculated considering that, in the worst case we need // to go from instance to class, from class to GType object and from // GType object to GType value. const GjsAtoms& atoms = GjsContextPrivate::atoms(cx); return actual_gtype_recurse(cx, atoms, object, gtype_out, 2); } }; JSObject* gjs_gtype_create_gtype_wrapper(JSContext* context, GType gtype) { return GTypeObj::create(context, gtype); } bool gjs_gtype_get_actual_gtype(JSContext* context, JS::HandleObject object, GType* gtype_out) { return GTypeObj::actual_gtype(context, object, gtype_out); }