@gjsify/node-gi 0.63.0 → 0.64.1
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/cairo.d.ts +25 -0
- package/cairo.js +23 -0
- package/gi.js +380 -122
- package/index.d.ts +13 -11
- package/index.js +1 -0
- package/overrides/gio-settings.js +2 -24
- 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-arm64/node_gi.node +0 -0
- package/prebuilds/win32-x64/node_gi.node +0 -0
- package/src/addon.cc +1 -0
- package/src/cairo.cc +66 -1
- package/src/calls.cc +84 -63
- package/src/class.cc +2 -2
- package/src/common.h +174 -28
- package/src/marshal.cc +93 -111
- package/src/object.cc +79 -10
- package/src/private.cc +33 -0
- package/src/repo.cc +43 -15
package/index.d.ts
CHANGED
|
@@ -56,17 +56,18 @@ export function getEnumValues(namespace: string, name: string): Record<string, n
|
|
|
56
56
|
export function getErrorDomain(namespace: string, name: string): { name: string; quark: number } | null;
|
|
57
57
|
|
|
58
58
|
/**
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
59
|
+
* Resolve a domain quark back to the enum type registered for it — the reverse of
|
|
60
|
+
* {@link getErrorDomain}, so a thrown error reaches its domain class even when
|
|
61
|
+
* nothing has touched that enum yet. `null` for a quark no typelib claims.
|
|
62
|
+
*/
|
|
63
|
+
export function errorDomainInfo(quark: number): { namespace: string; name: string } | null;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Register the L1 `GLib.Error` factory the engine calls when a GI invoke fails. It
|
|
67
|
+
* receives the failed call's `GError` as a boxed handle, having taken ownership of
|
|
68
|
+
* it, and returns the object to throw — the same shape a marshalled error gets.
|
|
69
|
+
*/
|
|
70
|
+
export function setErrorBuilder(builder: (handle: BoxedHandle) => unknown): void;
|
|
70
71
|
|
|
71
72
|
/** Prepend a directory to the GIRepository typelib search path. */
|
|
72
73
|
export function prependSearchPath(path: string): void;
|
|
@@ -599,6 +600,7 @@ declare const native: {
|
|
|
599
600
|
getConstantValue: typeof getConstantValue;
|
|
600
601
|
getEnumValues: typeof getEnumValues;
|
|
601
602
|
getErrorDomain: typeof getErrorDomain;
|
|
603
|
+
errorDomainInfo: typeof errorDomainInfo;
|
|
602
604
|
setErrorBuilder: typeof setErrorBuilder;
|
|
603
605
|
prependSearchPath: typeof prependSearchPath;
|
|
604
606
|
prependLibraryPath: typeof prependLibraryPath;
|
package/index.js
CHANGED
|
@@ -29,6 +29,7 @@ export const findInfo = native.findInfo;
|
|
|
29
29
|
export const getConstantValue = native.getConstantValue;
|
|
30
30
|
export const getEnumValues = native.getEnumValues;
|
|
31
31
|
export const getErrorDomain = native.getErrorDomain;
|
|
32
|
+
export const errorDomainInfo = native.errorDomainInfo;
|
|
32
33
|
export const setErrorBuilder = native.setErrorBuilder;
|
|
33
34
|
export const prependSearchPath = native.prependSearchPath;
|
|
34
35
|
export const prependLibraryPath = native.prependLibraryPath;
|
|
@@ -10,16 +10,8 @@
|
|
|
10
10
|
// throws; this is the same wrapper, so `try { … } catch` means the same on either runtime
|
|
11
11
|
// (ADR 0099 conformance row `errors`).
|
|
12
12
|
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
// • `set_int` / `set_uint` range-check their value, which in GJS is the argument marshaller's
|
|
16
|
-
// job. node-gi's marshaller wraps modulo 2^32 (`set_uint('k', -1)` stores 4294967295), a
|
|
17
|
-
// general divergence recorded in status/open-todos/node-gi.md; this keeps the settings
|
|
18
|
-
// surface honest without touching the addon, so it also holds in an already-built payload.
|
|
19
|
-
|
|
20
|
-
const INT32_MIN = -(2 ** 31);
|
|
21
|
-
const INT32_MAX = 2 ** 31 - 1;
|
|
22
|
-
const UINT32_MAX = 2 ** 32 - 1;
|
|
13
|
+
// One deliberate difference from the GJS override: the constructor check is a Proxy `construct`
|
|
14
|
+
// trap — a node-gi class has no `_init` to wrap.
|
|
23
15
|
|
|
24
16
|
const CHECKED = [
|
|
25
17
|
'get_boolean',
|
|
@@ -53,12 +45,6 @@ const CHECKED = [
|
|
|
53
45
|
'reset',
|
|
54
46
|
];
|
|
55
47
|
|
|
56
|
-
/** The range a setter's JS number must be in; GJS's marshaller throws this exact message. */
|
|
57
|
-
const RANGES = {
|
|
58
|
-
set_int: [INT32_MIN, INT32_MAX, 'int32'],
|
|
59
|
-
set_uint: [0, UINT32_MAX, 'uint32'],
|
|
60
|
-
};
|
|
61
|
-
|
|
62
48
|
/**
|
|
63
49
|
* Patch `Gio.Settings` / `Gio.SettingsSchema` like GJS does, once, and return the class to hand
|
|
64
50
|
* out (a Proxy whose `new` refuses a schema that is not installed instead of aborting).
|
|
@@ -87,16 +73,8 @@ export function createGioSettings(Gio) {
|
|
|
87
73
|
for (const name of CHECKED) {
|
|
88
74
|
const real = proto[name];
|
|
89
75
|
if (typeof real !== 'function') continue;
|
|
90
|
-
const range = RANGES[name];
|
|
91
76
|
proto[name] = function (key, ...args) {
|
|
92
77
|
checkKey(this, key);
|
|
93
|
-
if (range !== undefined) {
|
|
94
|
-
const [min, max, type] = range;
|
|
95
|
-
const value = args[0];
|
|
96
|
-
if (typeof value !== 'number' || !Number.isFinite(value) || value < min || value > max) {
|
|
97
|
-
throw new Error(`Argument value: value is out of range for ${type}`);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
78
|
return real.call(this, key, ...args);
|
|
101
79
|
};
|
|
102
80
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjsify/node-gi",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.64.1",
|
|
4
4
|
"description": "GObject-Introspection runtime for Node.js — load gi:// namespaces (GLib/GObject/Gio/…) with GJS-compatible semantics. Vendored + rewritten from node-gtk under MIT.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/src/addon.cc
CHANGED
|
@@ -88,6 +88,7 @@ static Napi::Object Init(Napi::Env env, Napi::Object exports) {
|
|
|
88
88
|
exports.Set("getConstantValue", Napi::Function::New(env, GetConstantValue));
|
|
89
89
|
exports.Set("getEnumValues", Napi::Function::New(env, GetEnumValues));
|
|
90
90
|
exports.Set("getErrorDomain", Napi::Function::New(env, GetErrorDomain));
|
|
91
|
+
exports.Set("errorDomainInfo", Napi::Function::New(env, ErrorDomainInfo));
|
|
91
92
|
exports.Set("setErrorBuilder", Napi::Function::New(env, SetErrorBuilder));
|
|
92
93
|
exports.Set("prependSearchPath", Napi::Function::New(env, PrependSearchPath));
|
|
93
94
|
exports.Set("prependLibraryPath", Napi::Function::New(env, PrependLibraryPath));
|
package/src/cairo.cc
CHANGED
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
// WrapBoxed; cairo_path_t is NOT ref-counted, so a borrowed path is COPIED via
|
|
18
18
|
// the gjs CairoPath::copy_ptr dummy-context trick instead of ref'd). Slice:
|
|
19
19
|
// Context (drawing ops, path copy/append, dash, matrix point transforms,
|
|
20
|
-
// in-fill/in-stroke, getSource
|
|
20
|
+
// in-fill/in-stroke, getSource, the toy-text API: selectFontFace/setFontSize/
|
|
21
|
+
// showText/textPath/textExtents/fontExtents), Surface + ImageSurface (headless pixels),
|
|
21
22
|
// SolidPattern, LinearGradient/RadialGradient (color stops), SurfacePattern
|
|
22
23
|
// (extend/filter), Path handles. Deferred: region, PDF/SVG/PS surfaces,
|
|
23
24
|
// text/font ops (showText/selectFontFace — canvas2d text rides PangoCairo).
|
|
@@ -733,6 +734,70 @@ void InitCairo(Napi::Env env, Napi::Object exports) {
|
|
|
733
734
|
CTX_EXTENTS("fillExtents", cairo_fill_extents)
|
|
734
735
|
CTX_EXTENTS("strokeExtents", cairo_stroke_extents)
|
|
735
736
|
CTX_EXTENTS("clipExtents", cairo_clip_extents)
|
|
737
|
+
// Toy text API (cairo_select_font_face & co), the GJS-compatible Context text ops.
|
|
738
|
+
// textExtents/fontExtents return the object shape gjs's cairo-context.cpp builds.
|
|
739
|
+
c.Set("selectFontFace", Napi::Function::New(env, +[](const Napi::CallbackInfo& info) -> Napi::Value {
|
|
740
|
+
Napi::Env env = info.Env();
|
|
741
|
+
cairo_t* cr = CtxArg(env, info);
|
|
742
|
+
if (cr == nullptr) return env.Undefined();
|
|
743
|
+
std::string family = info[1].ToString().Utf8Value();
|
|
744
|
+
cairo_select_font_face(cr, family.c_str(),
|
|
745
|
+
static_cast<cairo_font_slant_t>(info[2].ToNumber().Int32Value()),
|
|
746
|
+
static_cast<cairo_font_weight_t>(info[3].ToNumber().Int32Value()));
|
|
747
|
+
CheckStatus(env, cairo_status(cr), "selectFontFace");
|
|
748
|
+
return env.Undefined();
|
|
749
|
+
}));
|
|
750
|
+
c.Set("setFontSize", Napi::Function::New(env, CTX_1D(cairo_set_font_size)));
|
|
751
|
+
c.Set("showText", Napi::Function::New(env, +[](const Napi::CallbackInfo& info) -> Napi::Value {
|
|
752
|
+
Napi::Env env = info.Env();
|
|
753
|
+
cairo_t* cr = CtxArg(env, info);
|
|
754
|
+
if (cr == nullptr) return env.Undefined();
|
|
755
|
+
std::string text = info[1].ToString().Utf8Value();
|
|
756
|
+
cairo_show_text(cr, text.c_str());
|
|
757
|
+
CheckStatus(env, cairo_status(cr), "showText");
|
|
758
|
+
return env.Undefined();
|
|
759
|
+
}));
|
|
760
|
+
c.Set("textPath", Napi::Function::New(env, +[](const Napi::CallbackInfo& info) -> Napi::Value {
|
|
761
|
+
Napi::Env env = info.Env();
|
|
762
|
+
cairo_t* cr = CtxArg(env, info);
|
|
763
|
+
if (cr == nullptr) return env.Undefined();
|
|
764
|
+
std::string text = info[1].ToString().Utf8Value();
|
|
765
|
+
cairo_text_path(cr, text.c_str());
|
|
766
|
+
CheckStatus(env, cairo_status(cr), "textPath");
|
|
767
|
+
return env.Undefined();
|
|
768
|
+
}));
|
|
769
|
+
c.Set("textExtents", Napi::Function::New(env, +[](const Napi::CallbackInfo& info) -> Napi::Value {
|
|
770
|
+
Napi::Env env = info.Env();
|
|
771
|
+
cairo_t* cr = CtxArg(env, info);
|
|
772
|
+
if (cr == nullptr) return env.Undefined();
|
|
773
|
+
std::string text = info[1].ToString().Utf8Value();
|
|
774
|
+
cairo_text_extents_t e;
|
|
775
|
+
cairo_text_extents(cr, text.c_str(), &e);
|
|
776
|
+
if (!CheckStatus(env, cairo_status(cr), "textExtents")) return env.Undefined();
|
|
777
|
+
Napi::Object o = Napi::Object::New(env);
|
|
778
|
+
o.Set("xBearing", Napi::Number::New(env, e.x_bearing));
|
|
779
|
+
o.Set("yBearing", Napi::Number::New(env, e.y_bearing));
|
|
780
|
+
o.Set("width", Napi::Number::New(env, e.width));
|
|
781
|
+
o.Set("height", Napi::Number::New(env, e.height));
|
|
782
|
+
o.Set("xAdvance", Napi::Number::New(env, e.x_advance));
|
|
783
|
+
o.Set("yAdvance", Napi::Number::New(env, e.y_advance));
|
|
784
|
+
return o;
|
|
785
|
+
}));
|
|
786
|
+
c.Set("fontExtents", Napi::Function::New(env, +[](const Napi::CallbackInfo& info) -> Napi::Value {
|
|
787
|
+
Napi::Env env = info.Env();
|
|
788
|
+
cairo_t* cr = CtxArg(env, info);
|
|
789
|
+
if (cr == nullptr) return env.Undefined();
|
|
790
|
+
cairo_font_extents_t e;
|
|
791
|
+
cairo_font_extents(cr, &e);
|
|
792
|
+
if (!CheckStatus(env, cairo_status(cr), "fontExtents")) return env.Undefined();
|
|
793
|
+
Napi::Object o = Napi::Object::New(env);
|
|
794
|
+
o.Set("ascent", Napi::Number::New(env, e.ascent));
|
|
795
|
+
o.Set("descent", Napi::Number::New(env, e.descent));
|
|
796
|
+
o.Set("height", Napi::Number::New(env, e.height));
|
|
797
|
+
o.Set("maxXAdvance", Napi::Number::New(env, e.max_x_advance));
|
|
798
|
+
o.Set("maxYAdvance", Napi::Number::New(env, e.max_y_advance));
|
|
799
|
+
return o;
|
|
800
|
+
}));
|
|
736
801
|
c.Set("status", Napi::Function::New(env, +[](const Napi::CallbackInfo& info) -> Napi::Value {
|
|
737
802
|
Napi::Env env = info.Env();
|
|
738
803
|
cairo_t* cr = CtxArg(env, info);
|
package/src/calls.cc
CHANGED
|
@@ -380,44 +380,40 @@ bool IsSupportedOutType(GITypeInfo* type, std::string* why) {
|
|
|
380
380
|
|
|
381
381
|
// ---- GError -> GLib.Error ----
|
|
382
382
|
//
|
|
383
|
-
// A failed GI invoke yields a GError (domain quark + code + message)
|
|
384
|
-
//
|
|
385
|
-
//
|
|
386
|
-
//
|
|
387
|
-
//
|
|
388
|
-
//
|
|
389
|
-
// what it returns.
|
|
383
|
+
// A failed GI invoke yields a GError (domain quark + code + message), which GJS
|
|
384
|
+
// throws as the error object of that DOMAIN's class. That object is L1's to build
|
|
385
|
+
// (gi.js wrapGError) — only JS can give it the Error brand, the domain class and
|
|
386
|
+
// the position properties — so the engine hands the builder registered in this
|
|
387
|
+
// env's instance data the one thing it needs: the `GError*` itself, as a boxed
|
|
388
|
+
// handle.
|
|
390
389
|
//
|
|
391
|
-
//
|
|
392
|
-
//
|
|
393
|
-
//
|
|
394
|
-
//
|
|
395
|
-
//
|
|
390
|
+
// Passing the HANDLE and not the fields is the whole point. The fields used to be
|
|
391
|
+
// read out, the GError g_error_free'd here, and a JS Error subclass built from
|
|
392
|
+
// them — so a thrown error and a marshalled one were two different objects with
|
|
393
|
+
// two different `.domain` types. Ownership now transfers to the handle, which
|
|
394
|
+
// frees the GError when the JS error is collected, and both directions wrap the
|
|
395
|
+
// same pointer the same way.
|
|
396
|
+
//
|
|
397
|
+
// Falls back to a plain JS Error when no builder is registered for this env;
|
|
398
|
+
// `context` is the "Ns.method" display name, used only in that fallback message (a
|
|
399
|
+
// real GLib.Error carries the bare GError message, matching GJS).
|
|
396
400
|
void ThrowGError(Napi::Env env, GError* error, const std::string& context) {
|
|
397
|
-
// Read everything out of the GError BEFORE g_error_free.
|
|
398
|
-
GQuark domainQuark = error != nullptr ? error->domain : 0;
|
|
399
|
-
const char* domainName = domainQuark != 0 ? g_quark_to_string(domainQuark) : nullptr;
|
|
400
|
-
std::string domain = domainName != nullptr ? domainName : "";
|
|
401
|
-
int code = error != nullptr ? error->code : 0;
|
|
402
401
|
std::string message =
|
|
403
402
|
(error != nullptr && error->message != nullptr) ? error->message : "invocation failed";
|
|
404
|
-
if (error != nullptr) g_error_free(error);
|
|
405
403
|
|
|
406
|
-
// Prefer this env's L1 GLib.Error builder (instanceof GLib.Error + matches()).
|
|
407
404
|
NodeGiEnvData* d = EnvData(env);
|
|
408
|
-
if (d != nullptr && d->errorBuilder != nullptr) {
|
|
405
|
+
if (error != nullptr && d != nullptr && d->errorBuilder != nullptr) {
|
|
409
406
|
napi_value builder = nullptr;
|
|
410
407
|
if (napi_get_reference_value(env, d->errorBuilder, &builder) == napi_ok &&
|
|
411
408
|
builder != nullptr) {
|
|
412
409
|
napi_value undef = nullptr;
|
|
413
410
|
napi_get_undefined(env, &undef);
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
napi_create_string_utf8(env, message.c_str(), NAPI_AUTO_LENGTH, &bargs[3]);
|
|
411
|
+
// GI_TRANSFER_EVERYTHING: the invoke's out-GError is ours, and the handle
|
|
412
|
+
// adopts it rather than copying — so nothing frees it here.
|
|
413
|
+
napi_value handle = MakeGErrorHandle(env, error, GI_TRANSFER_EVERYTHING);
|
|
414
|
+
error = nullptr; // adopted by the handle; no path below may free it again
|
|
419
415
|
napi_value errObj = nullptr;
|
|
420
|
-
napi_status st = napi_call_function(env, undef, builder,
|
|
416
|
+
napi_status st = napi_call_function(env, undef, builder, 1, &handle, &errObj);
|
|
421
417
|
if (st == napi_ok && errObj != nullptr) {
|
|
422
418
|
napi_throw(env, errObj);
|
|
423
419
|
return;
|
|
@@ -427,6 +423,7 @@ void ThrowGError(Napi::Env env, GError* error, const std::string& context) {
|
|
|
427
423
|
if (napi_is_exception_pending(env, &pending) == napi_ok && pending) return;
|
|
428
424
|
}
|
|
429
425
|
}
|
|
426
|
+
if (error != nullptr) g_error_free(error);
|
|
430
427
|
Napi::Error::New(env, "Calling " + context + ": " + message).ThrowAsJavaScriptException();
|
|
431
428
|
}
|
|
432
429
|
|
|
@@ -636,7 +633,6 @@ static Napi::Value InvokeFunctionInfo(Napi::Env env, GIFunctionInfo* func, gpoin
|
|
|
636
633
|
// belong to the callee (dropped only when the callee never ran).
|
|
637
634
|
CreatedBytes createdBytes;
|
|
638
635
|
CreatedValues createdValues;
|
|
639
|
-
CreatedErrors createdErrors;
|
|
640
636
|
bool ok = true;
|
|
641
637
|
size_t jsCursor = 0;
|
|
642
638
|
std::vector<LengthWrite> lengthWrites;
|
|
@@ -815,7 +811,7 @@ static Napi::Value InvokeFunctionInfo(Napi::Env env, GIFunctionInfo* func, gpoin
|
|
|
815
811
|
jsCursor++;
|
|
816
812
|
GITransfer tr = gi_arg_info_get_ownership_transfer(ai);
|
|
817
813
|
if (JsToGIArgument(env, v, ti, &slots[i], &held[i], tr, &ownedInStrings, nullptr,
|
|
818
|
-
nullptr, nullptr,
|
|
814
|
+
nullptr, nullptr,
|
|
819
815
|
gi_base_info_get_name(reinterpret_cast<GIBaseInfo*>(ai)))) {
|
|
820
816
|
in_args[inPos[i]].v_pointer = &slots[i];
|
|
821
817
|
out_args[outPos[i]].v_pointer = &slots[i];
|
|
@@ -922,7 +918,7 @@ static Napi::Value InvokeFunctionInfo(Napi::Env env, GIFunctionInfo* func, gpoin
|
|
|
922
918
|
} else {
|
|
923
919
|
GITransfer tr = gi_arg_info_get_ownership_transfer(ai);
|
|
924
920
|
ok = JsToGIArgument(env, v, ti, &in_args[inPos[i]], &held[i], tr, &ownedInStrings,
|
|
925
|
-
&createdClosures, &createdBytes, &createdValues,
|
|
921
|
+
&createdClosures, &createdBytes, &createdValues,
|
|
926
922
|
gi_base_info_get_name(reinterpret_cast<GIBaseInfo*>(ai)));
|
|
927
923
|
}
|
|
928
924
|
}
|
|
@@ -944,9 +940,6 @@ static Napi::Value InvokeFunctionInfo(Napi::Env env, GIFunctionInfo* func, gpoin
|
|
|
944
940
|
// Likewise every GValue boxed from a plain JS value — never adopted.
|
|
945
941
|
for (GValue* gv : createdValues.transferNone) NodeGiFreeBoxedGValue(gv);
|
|
946
942
|
for (GValue* gv : createdValues.transferFull) NodeGiFreeBoxedGValue(gv);
|
|
947
|
-
// Likewise every GError built from an L1 GLib.Error — never adopted.
|
|
948
|
-
for (GError* e : createdErrors.transferNone) g_error_free(e);
|
|
949
|
-
for (GError* e : createdErrors.transferFull) g_error_free(e);
|
|
950
943
|
// Likewise the transfer-full-instance ref: the callee never consumed it.
|
|
951
944
|
if (instanceRefTaken) g_object_unref(static_cast<GObject*>(instance));
|
|
952
945
|
for (gpointer s : ownedInStrings) g_free(s); // never reached the callee (#658)
|
|
@@ -1001,11 +994,6 @@ static Napi::Value InvokeFunctionInfo(Napi::Env env, GIFunctionInfo* func, gpoin
|
|
|
1001
994
|
// box is ours to free — success AND error paths, mirroring the GBytes rule.
|
|
1002
995
|
// Transfer-full GValues were adopted by the callee — no release.
|
|
1003
996
|
for (GValue* gv : createdValues.transferNone) NodeGiFreeBoxedGValue(gv);
|
|
1004
|
-
// Transfer-none GError IN-args built from an L1 GLib.Error: the callee copied
|
|
1005
|
-
// what it kept (gst_message_new_error copies the GError into the message), so
|
|
1006
|
-
// the build is ours to free — success AND error paths, mirroring the two rules
|
|
1007
|
-
// above. Transfer-full GErrors were adopted by the callee — no release.
|
|
1008
|
-
for (GError* e : createdErrors.transferNone) g_error_free(e);
|
|
1009
997
|
if (!success) {
|
|
1010
998
|
for (const InContainer& c : inContainers) FreeInContainer(c);
|
|
1011
999
|
// A failed invoke did not adopt the transfer-full IN/INOUT strings we g_strdup'd
|
|
@@ -1254,6 +1242,57 @@ static GIFunctionInfo* ResolveInstanceMethod(GIRepository* repo, GType gtype, GI
|
|
|
1254
1242
|
return func;
|
|
1255
1243
|
}
|
|
1256
1244
|
|
|
1245
|
+
// Resolved instance methods, keyed by the instance's concrete GType and the name
|
|
1246
|
+
// as JS spelled it. The resolution above is the expensive half of a GI call —
|
|
1247
|
+
// dup the default repository, walk the GType parents for the nearest
|
|
1248
|
+
// GIObjectInfo, then search own + interface + inherited method tables twice (the
|
|
1249
|
+
// literal name, then the snake_case alias) — and it repeats IDENTICALLY on every
|
|
1250
|
+
// call, because a typelib's method tables do not change at runtime. MEASURED on
|
|
1251
|
+
// this machine (gjs 1.88.1 / GLib 2.88.3): `set_property` on a registerClass
|
|
1252
|
+
// subclass cost 8224 ns through callMethod against 2815 ns for the same
|
|
1253
|
+
// g_object_set_property through setProperty — 5.4 µs of pure re-resolution per
|
|
1254
|
+
// call, which is most of what a property write on the real-GObject engine spends.
|
|
1255
|
+
//
|
|
1256
|
+
// Only a HIT is cached. A miss must stay uncached: namespaces load lazily, so a
|
|
1257
|
+
// name that resolves to nothing now can resolve after the typelib that declares
|
|
1258
|
+
// it is required — caching the miss would make load order change behaviour.
|
|
1259
|
+
//
|
|
1260
|
+
// The cache OWNS the ref it holds (GIFunctionInfos live as long as the process
|
|
1261
|
+
// anyway, and the entry count is bounded by the methods a program actually
|
|
1262
|
+
// calls); callers get a borrowed pointer and must not unref it. Written only
|
|
1263
|
+
// from the JS thread, like every other entry point here.
|
|
1264
|
+
static std::map<std::pair<GType, std::string>, GIFunctionInfo*> g_instanceMethodCache;
|
|
1265
|
+
|
|
1266
|
+
// The resolution of ResolveInstanceMethod, memoized. Returns a BORROWED pointer
|
|
1267
|
+
// (never unref) or nullptr.
|
|
1268
|
+
static GIFunctionInfo* ResolveInstanceMethodCached(GType gtype, const std::string& method) {
|
|
1269
|
+
const std::pair<GType, std::string> key(gtype, method);
|
|
1270
|
+
auto it = g_instanceMethodCache.find(key);
|
|
1271
|
+
if (it != g_instanceMethodCache.end()) return it->second;
|
|
1272
|
+
|
|
1273
|
+
GIRepository* repo = DupDefaultRepository();
|
|
1274
|
+
GIObjectInfo* objInfo = FindNearestObjectInfo(repo, gtype);
|
|
1275
|
+
GIFunctionInfo* func = nullptr;
|
|
1276
|
+
if (objInfo != nullptr) {
|
|
1277
|
+
func = ResolveInstanceMethod(repo, gtype, objInfo, method);
|
|
1278
|
+
gi_base_info_unref(objInfo);
|
|
1279
|
+
}
|
|
1280
|
+
g_object_unref(repo);
|
|
1281
|
+
if (func != nullptr) g_instanceMethodCache.emplace(key, func);
|
|
1282
|
+
return func;
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
// Whether `gtype` has introspection info at all — the distinction callMethod
|
|
1286
|
+
// reports as "no introspection info for X" before it even looks for a method.
|
|
1287
|
+
static bool HasObjectInfo(GType gtype) {
|
|
1288
|
+
GIRepository* repo = DupDefaultRepository();
|
|
1289
|
+
GIObjectInfo* objInfo = FindNearestObjectInfo(repo, gtype);
|
|
1290
|
+
const bool found = objInfo != nullptr;
|
|
1291
|
+
if (objInfo != nullptr) gi_base_info_unref(objInfo);
|
|
1292
|
+
g_object_unref(repo);
|
|
1293
|
+
return found;
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1257
1296
|
// callMethod(handle, methodName, args?: unknown[]) -> unknown
|
|
1258
1297
|
// Resolve an instance method by walking the instance's GIObjectInfo (own +
|
|
1259
1298
|
// implemented-interface methods at each level, then up the parent chain), then
|
|
@@ -1274,20 +1313,14 @@ Napi::Value CallMethod(const Napi::CallbackInfo& info) {
|
|
|
1274
1313
|
: Napi::Array::New(env, 0);
|
|
1275
1314
|
|
|
1276
1315
|
GType gtype = G_OBJECT_TYPE(obj);
|
|
1277
|
-
|
|
1316
|
+
GIFunctionInfo* func = ResolveInstanceMethodCached(gtype, method);
|
|
1278
1317
|
|
|
1279
|
-
|
|
1280
|
-
if (objInfo == nullptr) {
|
|
1281
|
-
g_object_unref(repo);
|
|
1318
|
+
if (func == nullptr && !HasObjectInfo(gtype)) {
|
|
1282
1319
|
Napi::Error::New(env, std::string("no introspection info for ") + G_OBJECT_TYPE_NAME(obj))
|
|
1283
1320
|
.ThrowAsJavaScriptException();
|
|
1284
1321
|
return env.Null();
|
|
1285
1322
|
}
|
|
1286
1323
|
|
|
1287
|
-
GIFunctionInfo* func = ResolveInstanceMethod(repo, gtype, objInfo, method);
|
|
1288
|
-
gi_base_info_unref(objInfo);
|
|
1289
|
-
g_object_unref(repo);
|
|
1290
|
-
|
|
1291
1324
|
if (func == nullptr) {
|
|
1292
1325
|
// On a terminating env the method-name read (info[1].Utf8Value) degrades to
|
|
1293
1326
|
// "" (a swallowed napi failure), so resolution misses and we land here — but
|
|
@@ -1302,15 +1335,13 @@ Napi::Value CallMethod(const Napi::CallbackInfo& info) {
|
|
|
1302
1335
|
return env.Null();
|
|
1303
1336
|
}
|
|
1304
1337
|
if (!gi_callable_info_is_method(reinterpret_cast<GICallableInfo*>(func))) {
|
|
1305
|
-
gi_base_info_unref(func);
|
|
1306
1338
|
Napi::TypeError::New(env, method + " is not an instance method").ThrowAsJavaScriptException();
|
|
1307
1339
|
return env.Null();
|
|
1308
1340
|
}
|
|
1309
1341
|
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
return result;
|
|
1342
|
+
// The cache owns `func`; this is a borrowed pointer.
|
|
1343
|
+
return InvokeFunctionInfo(env, func, obj, args,
|
|
1344
|
+
std::string(G_OBJECT_TYPE_NAME(obj)) + "." + method);
|
|
1314
1345
|
}
|
|
1315
1346
|
|
|
1316
1347
|
// hasMethod(handle, methodName) -> boolean
|
|
@@ -1333,20 +1364,10 @@ Napi::Value HasMethod(const Napi::CallbackInfo& info) {
|
|
|
1333
1364
|
if (obj == nullptr) return env.Null();
|
|
1334
1365
|
std::string method = info[1].As<Napi::String>().Utf8Value();
|
|
1335
1366
|
|
|
1336
|
-
|
|
1337
|
-
GIRepository* repo = DupDefaultRepository();
|
|
1338
|
-
GIObjectInfo* objInfo = FindNearestObjectInfo(repo, gtype);
|
|
1339
|
-
if (objInfo == nullptr) {
|
|
1340
|
-
g_object_unref(repo);
|
|
1341
|
-
return Napi::Boolean::New(env, false);
|
|
1342
|
-
}
|
|
1343
|
-
GIFunctionInfo* func = ResolveInstanceMethod(repo, gtype, objInfo, method);
|
|
1344
|
-
gi_base_info_unref(objInfo);
|
|
1345
|
-
g_object_unref(repo);
|
|
1367
|
+
GIFunctionInfo* func = ResolveInstanceMethodCached(G_OBJECT_TYPE(obj), method);
|
|
1346
1368
|
if (func == nullptr) return Napi::Boolean::New(env, false);
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
return Napi::Boolean::New(env, invocable);
|
|
1369
|
+
return Napi::Boolean::New(env,
|
|
1370
|
+
gi_callable_info_is_method(reinterpret_cast<GICallableInfo*>(func)));
|
|
1350
1371
|
}
|
|
1351
1372
|
|
|
1352
1373
|
// hasClassMethod(namespace, typeName, methodName) -> boolean
|
package/src/class.cc
CHANGED
|
@@ -907,7 +907,7 @@ static Napi::Value InvokeVFuncPointer(Napi::Env env, GObject* obj, GICallableInf
|
|
|
907
907
|
jsCursor++;
|
|
908
908
|
GITransfer tr = gi_arg_info_get_ownership_transfer(ai);
|
|
909
909
|
if (JsToGIArgument(env, v, ti, &slots[i], &holds[i], tr, &ownedInStrings, nullptr,
|
|
910
|
-
nullptr, nullptr,
|
|
910
|
+
nullptr, nullptr,
|
|
911
911
|
gi_base_info_get_name(reinterpret_cast<GIBaseInfo*>(ai))))
|
|
912
912
|
giArgs[1 + i].v_pointer = &slots[i];
|
|
913
913
|
else
|
|
@@ -963,7 +963,7 @@ static Napi::Value InvokeVFuncPointer(Napi::Env env, GObject* obj, GICallableInf
|
|
|
963
963
|
jsCursor++;
|
|
964
964
|
GITransfer tr = gi_arg_info_get_ownership_transfer(ai);
|
|
965
965
|
ok = JsToGIArgument(env, v, ti, &giArgs[1 + i], &holds[i], tr, &ownedInStrings, nullptr,
|
|
966
|
-
nullptr, nullptr,
|
|
966
|
+
nullptr, nullptr,
|
|
967
967
|
gi_base_info_get_name(reinterpret_cast<GIBaseInfo*>(ai)));
|
|
968
968
|
}
|
|
969
969
|
|