1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205
|
/* -*- 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 <config.h>
#include <glib-object.h>
#include <glib.h>
#include <js/CallArgs.h>
#include <js/Class.h>
#include <js/GCHashTable.h> // for WeakCache
#include <js/PropertyAndElement.h>
#include <js/PropertyDescriptor.h> // for JSPROP_PERMANENT
#include <js/PropertySpec.h>
#include <js/RootingAPI.h>
#include <js/TypeDecls.h>
#include <js/Value.h>
#include <jsapi.h> // for JS_NewObjectWithGivenProto
#include <mozilla/HashTable.h>
#include "gi/cwrapper.h"
#include "gi/gtype.h"
#include "cjs/atoms.h"
#include "cjs/context-private.h"
#include "cjs/global.h"
#include "cjs/jsapi-util.h"
#include "cjs/macros.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<GTypeObj, void> {
friend CWrapperPointerOps<GTypeObj, void>;
friend CWrapper<GTypeObj, void>;
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(JS::GCContext*, 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_RESERVED_SLOTS(1) | 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;
GTypeObj::init_private(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);
}
|