@gjsify/node-gi 0.43.0 → 0.45.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +121 -1092
- package/gi.js +101 -9
- package/gtk-runtime.d.ts +4 -1
- package/gtk-runtime.js +67 -8
- package/index.d.ts +39 -0
- package/index.js +3 -0
- package/package.json +1 -1
- package/prebuilds/darwin-arm64/node_gi.node +0 -0
- package/prebuilds/darwin-x64/node_gi.node +0 -0
- package/prebuilds/linux-arm64/node_gi.node +0 -0
- package/prebuilds/linux-x64/node_gi.node +0 -0
- package/prebuilds/win32-x64/node_gi.node +0 -0
- package/src/addon.cc +3 -0
- package/src/calls.cc +202 -66
- package/src/class.cc +240 -64
- package/src/common.h +11 -1
- package/src/marshal.cc +68 -1
package/src/class.cc
CHANGED
|
@@ -645,63 +645,18 @@ static void NodeGiClassInit(gpointer g_class, gpointer class_data) {
|
|
|
645
645
|
}
|
|
646
646
|
}
|
|
647
647
|
|
|
648
|
-
//
|
|
649
|
-
//
|
|
650
|
-
// Chain up to the parent implementation of an overridden vfunc — the engine half
|
|
651
|
-
// of `super.vfunc_<name>(...)`. Resolves the NodeGiVFunc record for `vfuncName`
|
|
652
|
-
// nearest the instance's type (the record whose trampoline currently owns the
|
|
653
|
-
// vtable slot) and ffi_call's its captured parentPtr — the function that was in
|
|
654
|
-
// the slot BEFORE the override was installed (the C default, or a JS override
|
|
655
|
-
// further up the chain). The same `cif` the override's closure was built from
|
|
656
|
-
// describes the call signature (instance + declared args → return), so it is
|
|
657
|
-
// reused to call out. `this` (args[0]) goes back in as the instance, keeping the
|
|
658
|
-
// canonical toggle-ref wrapper identity. Marshals IN args (JsToGIArgument) +
|
|
659
|
-
// the return (gi_type_info_extract_ffi_return_value → GIArgumentToJs); throws a
|
|
660
|
-
// GLib.Error for a can-throw vfunc whose parent set the GError.
|
|
661
|
-
//
|
|
662
|
-
// OUT / INOUT args: routed through per-arg storage slots exactly like the
|
|
663
|
-
// function-invoke path (calls.cc InvokeFunctionInfo). The parent's ffi signature
|
|
664
|
-
// takes a POINTER for each OUT/INOUT param, so giArgs[1+i] carries &slots[i] (the
|
|
665
|
-
// stable storage the C parent writes THROUGH) — INOUT slots are pre-marshalled from
|
|
666
|
-
// the JS input, OUT slots start zeroed; a caller-allocates OUT (fixed C array /
|
|
667
|
-
// boxed struct) gets a g_malloc0 blob instead. The JS caller passes only IN + INOUT
|
|
668
|
-
// args positionally (OUT params are engine-managed), and the result follows the GJS
|
|
669
|
-
// return-tuple convention `[returnValue?, ...outArgs]` — one value bare, several as
|
|
670
|
-
// an Array, matching exactly what a JS override of that vfunc receives as its call
|
|
671
|
-
// args. Read-back reuses ReadOutOrReturn (array-length slots, containers, boxed).
|
|
648
|
+
// ---- the shared vfunc CALL-OUT: marshal, ffi_call, assemble the GJS return ----
|
|
672
649
|
//
|
|
673
|
-
//
|
|
674
|
-
//
|
|
675
|
-
//
|
|
676
|
-
//
|
|
677
|
-
//
|
|
678
|
-
//
|
|
679
|
-
//
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
Napi::
|
|
683
|
-
Napi::Env env = info.Env();
|
|
684
|
-
if (info.Length() < 2 || !info[1].IsString()) {
|
|
685
|
-
Napi::TypeError::New(env, "callParentVfunc(handle, vfuncName: string, args?: unknown[])")
|
|
686
|
-
.ThrowAsJavaScriptException();
|
|
687
|
-
return env.Null();
|
|
688
|
-
}
|
|
689
|
-
GObject* obj = UnwrapGObject(env, info[0]);
|
|
690
|
-
if (obj == nullptr) return env.Null(); // UnwrapGObject threw or it was null
|
|
691
|
-
std::string vname = info[1].As<Napi::String>().Utf8Value();
|
|
692
|
-
Napi::Array args = (info.Length() >= 3 && info[2].IsArray()) ? info[2].As<Napi::Array>()
|
|
693
|
-
: Napi::Array::New(env, 0);
|
|
694
|
-
|
|
695
|
-
NodeGiVFunc* vf = FindDeepestVFuncRecord(G_OBJECT_TYPE(obj), vname);
|
|
696
|
-
if (vf == nullptr || vf->parentPtr == nullptr || vf->info == nullptr) {
|
|
697
|
-
Napi::Error::New(env, "no parent vfunc '" + vname + "' to chain up to on " +
|
|
698
|
-
g_type_name(G_OBJECT_TYPE(obj)) +
|
|
699
|
-
" (is it overridden by a registerClass subclass?)")
|
|
700
|
-
.ThrowAsJavaScriptException();
|
|
701
|
-
return env.Null();
|
|
702
|
-
}
|
|
703
|
-
|
|
704
|
-
GICallableInfo* ci = reinterpret_cast<GICallableInfo*>(vf->info);
|
|
650
|
+
// One body, two callers: `super.vfunc_x(...)` chaining to a captured parent slot
|
|
651
|
+
// (CallParentVfunc) and a plain introspected `inst.vfunc_x(...)` dispatching to the
|
|
652
|
+
// class's own slot (CallClassVfunc). Both have the same three inputs — the vfunc's
|
|
653
|
+
// GICallableInfo, an ffi_cif describing [instance, declared args, GError**] and the
|
|
654
|
+
// function pointer — so the ~300 lines of IN/OUT/INOUT marshalling below are shared
|
|
655
|
+
// rather than copied. `where` prefixes every diagnostic, so each caller names the
|
|
656
|
+
// spelling the program actually used.
|
|
657
|
+
static Napi::Value InvokeVFuncPointer(Napi::Env env, GObject* obj, GICallableInfo* ci,
|
|
658
|
+
ffi_cif* cif, gpointer fnPtr, const std::string& where,
|
|
659
|
+
Napi::Array args) {
|
|
705
660
|
unsigned int nDeclared = gi_callable_info_get_n_args(ci);
|
|
706
661
|
bool canThrow = gi_callable_info_can_throw_gerror(ci);
|
|
707
662
|
|
|
@@ -815,7 +770,7 @@ Napi::Value CallParentVfunc(const Napi::CallbackInfo& info) {
|
|
|
815
770
|
}
|
|
816
771
|
if (size == 0) {
|
|
817
772
|
Napi::TypeError::New(
|
|
818
|
-
env,
|
|
773
|
+
env, where + ": caller-allocates OUT parameter type is not yet supported")
|
|
819
774
|
.ThrowAsJavaScriptException();
|
|
820
775
|
ok = false;
|
|
821
776
|
} else {
|
|
@@ -825,7 +780,7 @@ Napi::Value CallParentVfunc(const Napi::CallbackInfo& info) {
|
|
|
825
780
|
giArgs[1 + i].v_pointer = blob;
|
|
826
781
|
}
|
|
827
782
|
} else if (!IsSupportedOutType(ti, &why)) {
|
|
828
|
-
Napi::TypeError::New(env,
|
|
783
|
+
Napi::TypeError::New(env, where + ": OUT " + why +
|
|
829
784
|
" parameters are not yet supported")
|
|
830
785
|
.ThrowAsJavaScriptException();
|
|
831
786
|
ok = false;
|
|
@@ -842,7 +797,7 @@ Napi::Value CallParentVfunc(const Napi::CallbackInfo& info) {
|
|
|
842
797
|
Napi::Value v = jsCursor < args.Length() ? args.Get(jsCursor) : env.Undefined();
|
|
843
798
|
jsCursor++;
|
|
844
799
|
if (!IsSupportedContainerType(ti, &why)) {
|
|
845
|
-
Napi::TypeError::New(env,
|
|
800
|
+
Napi::TypeError::New(env, where + ": INOUT " + why +
|
|
846
801
|
" parameters are not yet supported")
|
|
847
802
|
.ThrowAsJavaScriptException();
|
|
848
803
|
ok = false;
|
|
@@ -880,7 +835,8 @@ Napi::Value CallParentVfunc(const Napi::CallbackInfo& info) {
|
|
|
880
835
|
Napi::Value v = jsCursor < args.Length() ? args.Get(jsCursor) : env.Undefined();
|
|
881
836
|
jsCursor++;
|
|
882
837
|
GITransfer tr = gi_arg_info_get_ownership_transfer(ai);
|
|
883
|
-
if (JsToGIArgument(env, v, ti, &slots[i], &holds[i], tr, &ownedInStrings
|
|
838
|
+
if (JsToGIArgument(env, v, ti, &slots[i], &holds[i], tr, &ownedInStrings, nullptr,
|
|
839
|
+
nullptr, nullptr, gi_base_info_get_name(reinterpret_cast<GIBaseInfo*>(ai))))
|
|
884
840
|
giArgs[1 + i].v_pointer = &slots[i];
|
|
885
841
|
else
|
|
886
842
|
ok = false; // JsToGIArgument already threw
|
|
@@ -895,7 +851,7 @@ Napi::Value CallParentVfunc(const Napi::CallbackInfo& info) {
|
|
|
895
851
|
Napi::Value v = jsCursor < args.Length() ? args.Get(jsCursor) : env.Undefined();
|
|
896
852
|
jsCursor++;
|
|
897
853
|
if (!IsSupportedContainerType(ti, &why)) {
|
|
898
|
-
Napi::TypeError::New(env,
|
|
854
|
+
Napi::TypeError::New(env, where + ": IN " + why +
|
|
899
855
|
" parameters are not yet supported")
|
|
900
856
|
.ThrowAsJavaScriptException();
|
|
901
857
|
ok = false;
|
|
@@ -924,7 +880,8 @@ Napi::Value CallParentVfunc(const Napi::CallbackInfo& info) {
|
|
|
924
880
|
Napi::Value v = jsCursor < args.Length() ? args.Get(jsCursor) : env.Undefined();
|
|
925
881
|
jsCursor++;
|
|
926
882
|
GITransfer tr = gi_arg_info_get_ownership_transfer(ai);
|
|
927
|
-
ok = JsToGIArgument(env, v, ti, &giArgs[1 + i], &holds[i], tr, &ownedInStrings
|
|
883
|
+
ok = JsToGIArgument(env, v, ti, &giArgs[1 + i], &holds[i], tr, &ownedInStrings, nullptr,
|
|
884
|
+
nullptr, nullptr, gi_base_info_get_name(reinterpret_cast<GIBaseInfo*>(ai)));
|
|
928
885
|
}
|
|
929
886
|
|
|
930
887
|
avalue.push_back(&giArgs[1 + i]);
|
|
@@ -951,7 +908,7 @@ Napi::Value CallParentVfunc(const Napi::CallbackInfo& info) {
|
|
|
951
908
|
GITypeInfo* retType = gi_callable_info_get_return_type(ci);
|
|
952
909
|
GIFFIReturnValue ffiRet;
|
|
953
910
|
ffiRet.v_uint64 = 0;
|
|
954
|
-
ffi_call(
|
|
911
|
+
ffi_call(cif, reinterpret_cast<void (*)(void)>(fnPtr), &ffiRet, avalue.data());
|
|
955
912
|
|
|
956
913
|
if (canThrow && error != nullptr) {
|
|
957
914
|
gi_base_info_unref(retType);
|
|
@@ -959,7 +916,7 @@ Napi::Value CallParentVfunc(const Napi::CallbackInfo& info) {
|
|
|
959
916
|
for (gpointer s : ownedInStrings) g_free(s); // callee did not adopt them on error
|
|
960
917
|
for (gpointer b : callerAllocBlob)
|
|
961
918
|
if (b != nullptr) g_free(b);
|
|
962
|
-
ThrowGError(env, error,
|
|
919
|
+
ThrowGError(env, error, where);
|
|
963
920
|
return env.Null();
|
|
964
921
|
}
|
|
965
922
|
|
|
@@ -1028,6 +985,225 @@ Napi::Value CallParentVfunc(const Napi::CallbackInfo& info) {
|
|
|
1028
985
|
return arr;
|
|
1029
986
|
}
|
|
1030
987
|
|
|
988
|
+
// callParentVfunc(handle, vfuncName, args?) -> unknown
|
|
989
|
+
//
|
|
990
|
+
// Chain up to the parent implementation of an overridden vfunc — the engine half
|
|
991
|
+
// of `super.vfunc_<name>(...)`. Resolves the NodeGiVFunc record for `vfuncName`
|
|
992
|
+
// nearest the instance's type (the record whose trampoline currently owns the
|
|
993
|
+
// vtable slot) and ffi_call's its captured parentPtr — the function that was in
|
|
994
|
+
// the slot BEFORE the override was installed (the C default, or a JS override
|
|
995
|
+
// further up the chain). The same `cif` the override's closure was built from
|
|
996
|
+
// describes the call signature (instance + declared args → return), so it is
|
|
997
|
+
// reused to call out. `this` (args[0]) goes back in as the instance, keeping the
|
|
998
|
+
// canonical toggle-ref wrapper identity. Marshals IN args (JsToGIArgument) +
|
|
999
|
+
// the return (gi_type_info_extract_ffi_return_value → GIArgumentToJs); throws a
|
|
1000
|
+
// GLib.Error for a can-throw vfunc whose parent set the GError.
|
|
1001
|
+
//
|
|
1002
|
+
// OUT / INOUT args: routed through per-arg storage slots exactly like the
|
|
1003
|
+
// function-invoke path (calls.cc InvokeFunctionInfo). The parent's ffi signature
|
|
1004
|
+
// takes a POINTER for each OUT/INOUT param, so giArgs[1+i] carries &slots[i] (the
|
|
1005
|
+
// stable storage the C parent writes THROUGH) — INOUT slots are pre-marshalled from
|
|
1006
|
+
// the JS input, OUT slots start zeroed; a caller-allocates OUT (fixed C array /
|
|
1007
|
+
// boxed struct) gets a g_malloc0 blob instead. The JS caller passes only IN + INOUT
|
|
1008
|
+
// args positionally (OUT params are engine-managed), and the result follows the GJS
|
|
1009
|
+
// return-tuple convention `[returnValue?, ...outArgs]` — one value bare, several as
|
|
1010
|
+
// an Array, matching exactly what a JS override of that vfunc receives as its call
|
|
1011
|
+
// args. Read-back reuses ReadOutOrReturn (array-length slots, containers, boxed).
|
|
1012
|
+
//
|
|
1013
|
+
// MULTI-LEVEL chain-up (registered chains, G2): chains to the DEEPEST registered
|
|
1014
|
+
// override's captured parent (the C-side default below the whole registered chain),
|
|
1015
|
+
// NOT the nearest. On a multi-level chain the level-to-level `super.vfunc_<name>()`
|
|
1016
|
+
// hops are resolved by the JS PROTOTYPE chain (each registered ancestor's
|
|
1017
|
+
// `vfunc_<name>` is a real JS method, invoked directly), so every level's JS impl
|
|
1018
|
+
// has already run by the time the chain bottoms out at the introspected base's
|
|
1019
|
+
// prototype and reaches this thunk — at which point the only thing left to call is
|
|
1020
|
+
// the C default. Using the nearest record would re-enter an intermediate level's
|
|
1021
|
+
// trampoline (a double-run / infinite loop). See FindDeepestVFuncRecord.
|
|
1022
|
+
Napi::Value CallParentVfunc(const Napi::CallbackInfo& info) {
|
|
1023
|
+
Napi::Env env = info.Env();
|
|
1024
|
+
if (info.Length() < 2 || !info[1].IsString()) {
|
|
1025
|
+
Napi::TypeError::New(env, "callParentVfunc(handle, vfuncName: string, args?: unknown[])")
|
|
1026
|
+
.ThrowAsJavaScriptException();
|
|
1027
|
+
return env.Null();
|
|
1028
|
+
}
|
|
1029
|
+
GObject* obj = UnwrapGObject(env, info[0]);
|
|
1030
|
+
if (obj == nullptr) return env.Null(); // UnwrapGObject threw or it was null
|
|
1031
|
+
std::string vname = info[1].As<Napi::String>().Utf8Value();
|
|
1032
|
+
Napi::Array args = (info.Length() >= 3 && info[2].IsArray()) ? info[2].As<Napi::Array>()
|
|
1033
|
+
: Napi::Array::New(env, 0);
|
|
1034
|
+
|
|
1035
|
+
NodeGiVFunc* vf = FindDeepestVFuncRecord(G_OBJECT_TYPE(obj), vname);
|
|
1036
|
+
if (vf == nullptr || vf->parentPtr == nullptr || vf->info == nullptr) {
|
|
1037
|
+
Napi::Error::New(env, "no parent vfunc '" + vname + "' to chain up to on " +
|
|
1038
|
+
g_type_name(G_OBJECT_TYPE(obj)) +
|
|
1039
|
+
" (is it overridden by a registerClass subclass?)")
|
|
1040
|
+
.ThrowAsJavaScriptException();
|
|
1041
|
+
return env.Null();
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
return InvokeVFuncPointer(env, obj, reinterpret_cast<GICallableInfo*>(vf->info), &vf->cif,
|
|
1045
|
+
vf->parentPtr, "super." + vname, args);
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
// ---- vfunc dispatch on an INTROSPECTED class (no registerClass involved) ----
|
|
1049
|
+
//
|
|
1050
|
+
// `new Gtk.Box().vfunc_add_child(builder, child, null)` works on gjs, and it is the
|
|
1051
|
+
// only route to a GtkBuildable adder — `add_child` is introspected as a vfunc ONLY,
|
|
1052
|
+
// so there is no plain method to call (measured on gjs 1.88.1; ADR 0027 § Context
|
|
1053
|
+
// rests on it, and two React-for-GJS renderers route every insertion through it).
|
|
1054
|
+
// node-gi had ONLY the chain-up thunk, which needs a registerClass override record
|
|
1055
|
+
// to find a captured parent pointer, so every `vfunc_*` on a plain introspected
|
|
1056
|
+
// instance threw "no parent vfunc … to chain up to".
|
|
1057
|
+
//
|
|
1058
|
+
// gjs resolves such a name against the prototype's own GIObjectInfo, its implemented
|
|
1059
|
+
// interfaces and then its parents, and it resolves the ADDRESS at that prototype's
|
|
1060
|
+
// GType (gi/object.cpp find_vfunc_on_parents). This mirrors that: walk the object-info
|
|
1061
|
+
// parent chain, then ask girepository for the vtable slot of the class the prototype
|
|
1062
|
+
// belongs to. A resolution that FAILS is not an error here — it is the signal for
|
|
1063
|
+
// gi.js to fall back to the chain-up thunk, which is what keeps `super.vfunc_dispose()`
|
|
1064
|
+
// working (GObject's own vfuncs report GI_UNKNOWN as their struct offset, so
|
|
1065
|
+
// gi_vfunc_info_get_address cannot locate them).
|
|
1066
|
+
|
|
1067
|
+
// One resolved, immediately callable class vfunc. `usable` false = no such vfunc on
|
|
1068
|
+
// this class, or girepository cannot locate its slot for this GType.
|
|
1069
|
+
struct NodeGiClassVFunc {
|
|
1070
|
+
GIVFuncInfo* info = nullptr; // owned; process lifetime (a GIBaseInfo is immutable)
|
|
1071
|
+
GIFunctionInvoker invoker{}; // cif + native_address, ffi_prep'd once
|
|
1072
|
+
GType gtype = G_TYPE_INVALID; // the implementor whose slot `invoker` points into
|
|
1073
|
+
bool usable = false;
|
|
1074
|
+
};
|
|
1075
|
+
|
|
1076
|
+
// Cached per (namespace, typeName, vfuncName): the ffi cif behind an invoker costs a
|
|
1077
|
+
// malloc to build and a renderer calls vfunc_add_child on every insertion. thread_local
|
|
1078
|
+
// because GIBaseInfo refcounting is not thread-safe and a worker_threads env is its own
|
|
1079
|
+
// JS world with its own repository handles.
|
|
1080
|
+
static thread_local std::map<std::string, NodeGiClassVFunc> g_classVFuncs;
|
|
1081
|
+
|
|
1082
|
+
// Walk the object-info PARENT chain, searching each level's own vfuncs AND the
|
|
1083
|
+
// interfaces that level implements (gi_object_info_find_vfunc_using_interfaces —
|
|
1084
|
+
// `add_child` lives on the GtkBuildable INTERFACE, not on GtkBox). Returns a new
|
|
1085
|
+
// ref, or nullptr when no ancestor declares `name`.
|
|
1086
|
+
static GIVFuncInfo* FindVFuncOnClassOrParents(GIObjectInfo* start, const char* name) {
|
|
1087
|
+
GIObjectInfo* walk =
|
|
1088
|
+
reinterpret_cast<GIObjectInfo*>(gi_base_info_ref(reinterpret_cast<GIBaseInfo*>(start)));
|
|
1089
|
+
GIVFuncInfo* found = nullptr;
|
|
1090
|
+
while (walk != nullptr && found == nullptr) {
|
|
1091
|
+
// The declarer out-param is optional and we do not need it: the ADDRESS is resolved
|
|
1092
|
+
// against the class the PROTOTYPE belongs to, never against the declaring level.
|
|
1093
|
+
found = gi_object_info_find_vfunc_using_interfaces(walk, name, nullptr);
|
|
1094
|
+
GIObjectInfo* parent = found == nullptr ? gi_object_info_get_parent(walk) : nullptr;
|
|
1095
|
+
gi_base_info_unref(reinterpret_cast<GIBaseInfo*>(walk));
|
|
1096
|
+
walk = parent;
|
|
1097
|
+
}
|
|
1098
|
+
return found;
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
static NodeGiClassVFunc* ResolveClassVFunc(const std::string& ns, const std::string& tn,
|
|
1102
|
+
const std::string& vfuncName) {
|
|
1103
|
+
const std::string key = ns + "." + tn + "." + vfuncName;
|
|
1104
|
+
auto it = g_classVFuncs.find(key);
|
|
1105
|
+
if (it != g_classVFuncs.end()) return &it->second;
|
|
1106
|
+
|
|
1107
|
+
NodeGiClassVFunc entry;
|
|
1108
|
+
GIRepository* repo = DupDefaultRepository();
|
|
1109
|
+
GIBaseInfo* base = gi_repository_find_by_name(repo, ns.c_str(), tn.c_str());
|
|
1110
|
+
if (base != nullptr) {
|
|
1111
|
+
if (GI_IS_OBJECT_INFO(base)) {
|
|
1112
|
+
GIObjectInfo* oi = reinterpret_cast<GIObjectInfo*>(base);
|
|
1113
|
+
GType gtype =
|
|
1114
|
+
gi_registered_type_info_get_g_type(reinterpret_cast<GIRegisteredTypeInfo*>(oi));
|
|
1115
|
+
GIVFuncInfo* vi = FindVFuncOnClassOrParents(oi, vfuncName.c_str());
|
|
1116
|
+
if (vi != nullptr && gtype != G_TYPE_INVALID && G_TYPE_IS_CLASSED(gtype)) {
|
|
1117
|
+
// gi_vfunc_info_get_address reads the slot out of the LIVE class struct, which
|
|
1118
|
+
// exists only once the class has been referenced — and the ref is KEPT so the
|
|
1119
|
+
// vtable the cached address points into cannot be torn down under us. gjs holds
|
|
1120
|
+
// classes the same way; an instantiable GObject class lives for the process.
|
|
1121
|
+
g_type_class_ref(gtype);
|
|
1122
|
+
GError* err = nullptr;
|
|
1123
|
+
void* addr = gi_vfunc_info_get_address(vi, gtype, &err);
|
|
1124
|
+
if (addr != nullptr &&
|
|
1125
|
+
gi_function_invoker_new_for_address(addr, reinterpret_cast<GICallableInfo*>(vi),
|
|
1126
|
+
&entry.invoker, &err)) {
|
|
1127
|
+
entry.info = reinterpret_cast<GIVFuncInfo*>(
|
|
1128
|
+
gi_base_info_ref(reinterpret_cast<GIBaseInfo*>(vi)));
|
|
1129
|
+
entry.gtype = gtype;
|
|
1130
|
+
entry.usable = true;
|
|
1131
|
+
}
|
|
1132
|
+
g_clear_error(&err);
|
|
1133
|
+
}
|
|
1134
|
+
if (vi != nullptr) gi_base_info_unref(reinterpret_cast<GIBaseInfo*>(vi));
|
|
1135
|
+
}
|
|
1136
|
+
gi_base_info_unref(base);
|
|
1137
|
+
}
|
|
1138
|
+
g_object_unref(repo);
|
|
1139
|
+
return &g_classVFuncs.emplace(key, entry).first->second;
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
// hasClassVfunc(namespace, typeName, vfuncName) -> boolean
|
|
1143
|
+
//
|
|
1144
|
+
// Whether `Ns.Type.prototype.vfunc_<name>` can be MATERIALIZED as a direct call — the
|
|
1145
|
+
// gate gi.js asks before defining one, so a name that resolves to nothing stays
|
|
1146
|
+
// `undefined` (gjs parity: an unknown vfunc is undefined, never a throw-on-call thunk)
|
|
1147
|
+
// and a name we cannot address falls through to the chain-up thunk.
|
|
1148
|
+
Napi::Value HasClassVfunc(const Napi::CallbackInfo& info) {
|
|
1149
|
+
Napi::Env env = info.Env();
|
|
1150
|
+
if (info.Length() < 3 || !info[0].IsString() || !info[1].IsString() || !info[2].IsString()) {
|
|
1151
|
+
Napi::TypeError::New(env,
|
|
1152
|
+
"hasClassVfunc(namespace: string, typeName: string, vfuncName: string)")
|
|
1153
|
+
.ThrowAsJavaScriptException();
|
|
1154
|
+
return env.Null();
|
|
1155
|
+
}
|
|
1156
|
+
NodeGiClassVFunc* entry = ResolveClassVFunc(info[0].As<Napi::String>().Utf8Value(),
|
|
1157
|
+
info[1].As<Napi::String>().Utf8Value(),
|
|
1158
|
+
info[2].As<Napi::String>().Utf8Value());
|
|
1159
|
+
return Napi::Boolean::New(env, entry->usable);
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
// callClassVfunc(handle, namespace, typeName, vfuncName, args?) -> unknown
|
|
1163
|
+
//
|
|
1164
|
+
// Invoke the vtable entry `Ns.Type` carries for `vfuncName`, with `handle` as the
|
|
1165
|
+
// instance. Same marshalling and same GJS return-tuple convention as the chain-up
|
|
1166
|
+
// path (InvokeVFuncPointer); the only difference is where the function pointer came
|
|
1167
|
+
// from.
|
|
1168
|
+
Napi::Value CallClassVfunc(const Napi::CallbackInfo& info) {
|
|
1169
|
+
Napi::Env env = info.Env();
|
|
1170
|
+
if (info.Length() < 4 || !info[1].IsString() || !info[2].IsString() || !info[3].IsString()) {
|
|
1171
|
+
Napi::TypeError::New(
|
|
1172
|
+
env, "callClassVfunc(handle, namespace: string, typeName: string, vfuncName: string, "
|
|
1173
|
+
"args?: unknown[])")
|
|
1174
|
+
.ThrowAsJavaScriptException();
|
|
1175
|
+
return env.Null();
|
|
1176
|
+
}
|
|
1177
|
+
GObject* obj = UnwrapGObject(env, info[0]);
|
|
1178
|
+
if (obj == nullptr) return env.Null(); // UnwrapGObject threw or it was null
|
|
1179
|
+
const std::string ns = info[1].As<Napi::String>().Utf8Value();
|
|
1180
|
+
const std::string tn = info[2].As<Napi::String>().Utf8Value();
|
|
1181
|
+
const std::string vname = info[3].As<Napi::String>().Utf8Value();
|
|
1182
|
+
Napi::Array args = (info.Length() >= 5 && info[4].IsArray()) ? info[4].As<Napi::Array>()
|
|
1183
|
+
: Napi::Array::New(env, 0);
|
|
1184
|
+
const std::string where = ns + "." + tn + ".prototype.vfunc_" + vname;
|
|
1185
|
+
|
|
1186
|
+
NodeGiClassVFunc* entry = ResolveClassVFunc(ns, tn, vname);
|
|
1187
|
+
if (!entry->usable) {
|
|
1188
|
+
Napi::Error::New(env, where + ": no addressable virtual function on " + ns + "." + tn)
|
|
1189
|
+
.ThrowAsJavaScriptException();
|
|
1190
|
+
return env.Null();
|
|
1191
|
+
}
|
|
1192
|
+
// The cif was prepared for THIS class's slot, so an instance of an unrelated type
|
|
1193
|
+
// would hand C a wrong `self` and crash the process rather than throw. A detached
|
|
1194
|
+
// `Ns.Type.prototype.vfunc_x.call(other)` is the reachable spelling.
|
|
1195
|
+
if (!g_type_is_a(G_OBJECT_TYPE(obj), entry->gtype)) {
|
|
1196
|
+
Napi::TypeError::New(env, where + ": `this` is a " +
|
|
1197
|
+
g_type_name(G_OBJECT_TYPE(obj)) + ", not a " +
|
|
1198
|
+
g_type_name(entry->gtype))
|
|
1199
|
+
.ThrowAsJavaScriptException();
|
|
1200
|
+
return env.Null();
|
|
1201
|
+
}
|
|
1202
|
+
return InvokeVFuncPointer(env, obj, reinterpret_cast<GICallableInfo*>(entry->info),
|
|
1203
|
+
&entry->invoker.cif, entry->invoker.native_address, where, args);
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
|
|
1031
1207
|
// Shared registration core: subclass `name` from an ALREADY-RESOLVED parent GObject
|
|
1032
1208
|
// GType, reading the optional { properties, signals, vfuncs, template, children, ... }
|
|
1033
1209
|
// from `optsValue` (a non-object → no options). Returns the tagged GType handle for
|
package/src/common.h
CHANGED
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
|
|
33
33
|
#include <atomic>
|
|
34
34
|
#include <deque>
|
|
35
|
+
#include <map>
|
|
35
36
|
#include <memory>
|
|
36
37
|
#include <mutex>
|
|
37
38
|
#include <string>
|
|
@@ -339,13 +340,17 @@ GClosure* NodeGiMakeGenericJsClosure(Napi::Env env, Napi::Value fn);
|
|
|
339
340
|
// `values` (optional): enables the JS-value→GValue IN-arg boxing above; nullptr
|
|
340
341
|
// (the default) keeps the previous behaviour (plain value → TypeError) on paths
|
|
341
342
|
// that cannot release a created GValue.
|
|
343
|
+
// `argName` (optional): the introspected argument name, used ONLY in error
|
|
344
|
+
// messages so a refusal reads like gjs's ("Expected type string for argument
|
|
345
|
+
// 'name' but got type number"); nullptr degrades to a message without the name.
|
|
342
346
|
bool JsToGIArgument(Napi::Env env, Napi::Value v, GITypeInfo* type, GIArgument* out,
|
|
343
347
|
std::string* heldString,
|
|
344
348
|
GITransfer transfer = GI_TRANSFER_NOTHING,
|
|
345
349
|
std::vector<gpointer>* ownedStrings = nullptr,
|
|
346
350
|
CreatedClosures* closures = nullptr,
|
|
347
351
|
CreatedBytes* bytes = nullptr,
|
|
348
|
-
CreatedValues* values = nullptr
|
|
352
|
+
CreatedValues* values = nullptr,
|
|
353
|
+
const char* argName = nullptr);
|
|
349
354
|
|
|
350
355
|
// ---- IN container building -----------------------------------------
|
|
351
356
|
//
|
|
@@ -557,6 +562,9 @@ Napi::Value CallFunction(const Napi::CallbackInfo& info);
|
|
|
557
562
|
Napi::Value CallMethod(const Napi::CallbackInfo& info);
|
|
558
563
|
Napi::Value HasMethod(const Napi::CallbackInfo& info);
|
|
559
564
|
Napi::Value HasClassMethod(const Napi::CallbackInfo& info);
|
|
565
|
+
Napi::Value ClassMethodArity(const Napi::CallbackInfo& info);
|
|
566
|
+
// The JS-visible IN-arg count of a callable — gjs's `Function.length` (see calls.cc).
|
|
567
|
+
int JsInArgCount(GICallableInfo* callable);
|
|
560
568
|
// The nearest ancestor GType carrying object introspection info (a concrete type
|
|
561
569
|
// may be private — GLocalFile); a new ref, or nullptr. Shared with object.cc so
|
|
562
570
|
// method dispatch and prototype lookup resolve to the same class.
|
|
@@ -604,6 +612,8 @@ Napi::Value RegisterClass(const Napi::CallbackInfo& info);
|
|
|
604
612
|
Napi::Value RegisterClassFromGType(const Napi::CallbackInfo& info);
|
|
605
613
|
Napi::Value ConstructType(const Napi::CallbackInfo& info);
|
|
606
614
|
Napi::Value CallParentVfunc(const Napi::CallbackInfo& info);
|
|
615
|
+
Napi::Value HasClassVfunc(const Napi::CallbackInfo& info);
|
|
616
|
+
Napi::Value CallClassVfunc(const Napi::CallbackInfo& info);
|
|
607
617
|
Napi::Value SetConstructCallback(const Napi::CallbackInfo& info);
|
|
608
618
|
|
|
609
619
|
// template.cc
|
package/src/marshal.cc
CHANGED
|
@@ -424,6 +424,40 @@ static bool JsBinaryData(Napi::Env env, Napi::Value v, const uint8_t** data, siz
|
|
|
424
424
|
return false;
|
|
425
425
|
}
|
|
426
426
|
|
|
427
|
+
// The name SpiderMonkey's JS::InformalValueTypeName gives a value — what gjs
|
|
428
|
+
// interpolates into its marshalling errors. Primitives report their typeof;
|
|
429
|
+
// objects report their class name, which for the shapes reachable here is
|
|
430
|
+
// enough at "Object"/"Function".
|
|
431
|
+
static const char* InformalValueTypeName(const Napi::Value& v) {
|
|
432
|
+
if (v.IsNull()) return "null";
|
|
433
|
+
if (v.IsUndefined()) return "undefined";
|
|
434
|
+
if (v.IsBoolean()) return "boolean";
|
|
435
|
+
if (v.IsNumber()) return "number";
|
|
436
|
+
if (v.IsBigInt()) return "bigint";
|
|
437
|
+
if (v.IsString()) return "string";
|
|
438
|
+
if (v.IsSymbol()) return "symbol";
|
|
439
|
+
if (v.IsFunction()) return "Function";
|
|
440
|
+
if (v.IsArray()) return "Array";
|
|
441
|
+
return "Object";
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// "Gtk.Box" for an introspected GType, g_type_name() otherwise (a registerClass
|
|
445
|
+
// subtype has no GI info of its own; its C name is still exact). The gjs twin is
|
|
446
|
+
// GIWrapperBase::format_name() (refs/gjs/gi/wrapperutils.h).
|
|
447
|
+
static std::string GTypeDisplayName(GType gtype) {
|
|
448
|
+
GIRepository* repo = DupDefaultRepository();
|
|
449
|
+
GIBaseInfo* info = gi_repository_find_by_gtype(repo, gtype);
|
|
450
|
+
std::string name;
|
|
451
|
+
if (info != nullptr) {
|
|
452
|
+
name = std::string(gi_base_info_get_namespace(info)) + "." + gi_base_info_get_name(info);
|
|
453
|
+
gi_base_info_unref(info);
|
|
454
|
+
} else {
|
|
455
|
+
name = g_type_name(gtype);
|
|
456
|
+
}
|
|
457
|
+
g_object_unref(repo);
|
|
458
|
+
return name;
|
|
459
|
+
}
|
|
460
|
+
|
|
427
461
|
// `ownedStrings` (optional): when a transfer-full string IN/INOUT arg is g_strdup'd
|
|
428
462
|
// here, the freshly-allocated pointer is appended so the caller can g_free it if the
|
|
429
463
|
// invoke never adopts it (an arg-marshal error before the call, or a failed invoke).
|
|
@@ -434,7 +468,8 @@ bool JsToGIArgument(Napi::Env env, Napi::Value v, GITypeInfo* type, GIArgument*
|
|
|
434
468
|
std::vector<gpointer>* ownedStrings,
|
|
435
469
|
CreatedClosures* closures,
|
|
436
470
|
CreatedBytes* bytes,
|
|
437
|
-
CreatedValues* values
|
|
471
|
+
CreatedValues* values,
|
|
472
|
+
const char* argName) {
|
|
438
473
|
if (v.IsEmpty()) {
|
|
439
474
|
// Residue of a swallowed napi failure (a fallible Get()/coercion upstream
|
|
440
475
|
// failed on a terminating env, or a throwing getter left the exception
|
|
@@ -472,6 +507,19 @@ bool JsToGIArgument(Napi::Env env, Napi::Value v, GITypeInfo* type, GIArgument*
|
|
|
472
507
|
out->v_string = nullptr;
|
|
473
508
|
return true;
|
|
474
509
|
}
|
|
510
|
+
if (!v.IsString()) {
|
|
511
|
+
// gjs refuses any non-string here rather than coercing (arg-cache.cpp
|
|
512
|
+
// report_typeof_mismatch) — coercion is how `add_titled(child, 5, …)`
|
|
513
|
+
// silently registered a stack page named "5" while the same call threw
|
|
514
|
+
// on gjs, so @gjsify/gtk-host's refused-layout-write recovery never ran.
|
|
515
|
+
// Thrown as a plain Error, not TypeError: gjs_throw builds JSEXN_ERR.
|
|
516
|
+
std::string msg = std::string("Expected type string for ") +
|
|
517
|
+
(argName != nullptr ? std::string("argument '") + argName + "'"
|
|
518
|
+
: std::string("argument")) +
|
|
519
|
+
" but got type " + InformalValueTypeName(v);
|
|
520
|
+
Napi::Error::New(env, msg).ThrowAsJavaScriptException();
|
|
521
|
+
return false;
|
|
522
|
+
}
|
|
475
523
|
if (transfer == GI_TRANSFER_EVERYTHING) {
|
|
476
524
|
// The callee adopts the string and g_free's it. heldString points into a
|
|
477
525
|
// std::string buffer (NOT g_malloc'd) → an invalid free. Hand over a
|
|
@@ -497,6 +545,25 @@ bool JsToGIArgument(Napi::Env env, Napi::Value v, GITypeInfo* type, GIArgument*
|
|
|
497
545
|
handled = true;
|
|
498
546
|
} else if (v.IsExternal()) {
|
|
499
547
|
GObject* obj = v.As<Napi::External<GObject>>().Data();
|
|
548
|
+
// Runtime type check, as gjs (wrapperutils.h GIWrapperBase::typecheck):
|
|
549
|
+
// handing GTK a pointer of the wrong GType is not an error IT reports
|
|
550
|
+
// as an exception — adw_preferences_page_add() logs one CRITICAL and
|
|
551
|
+
// returns at exit 0, so the caller's recovery path never runs. Only a
|
|
552
|
+
// handle carrying the GObject type tag can be dereferenced safely (a
|
|
553
|
+
// fundamental handle also lands here and keeps the old pass-through).
|
|
554
|
+
if (v.As<Napi::External<GObject>>().CheckTypeTag(&kGObjectHandleTag) &&
|
|
555
|
+
obj != nullptr) {
|
|
556
|
+
GType expected = gi_registered_type_info_get_g_type(
|
|
557
|
+
reinterpret_cast<GIRegisteredTypeInfo*>(iface));
|
|
558
|
+
if (expected != G_TYPE_INVALID && expected != G_TYPE_NONE &&
|
|
559
|
+
!g_type_is_a(G_OBJECT_TYPE(obj), expected)) {
|
|
560
|
+
std::string msg = "Object is of type " + GTypeDisplayName(G_OBJECT_TYPE(obj)) +
|
|
561
|
+
" - cannot convert to " + g_type_name(expected);
|
|
562
|
+
gi_base_info_unref(iface);
|
|
563
|
+
Napi::TypeError::New(env, msg).ThrowAsJavaScriptException();
|
|
564
|
+
return false;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
500
567
|
// A (transfer full) GObject IN arg CONSUMES a reference: the callee
|
|
501
568
|
// takes over the one it is handed. Passing the handle's own ref
|
|
502
569
|
// makes the callee and the JS wrapper share a single ref that BOTH
|