@gjsify/node-gi 0.23.0 → 0.24.2
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/gi.d.ts +1 -1
- package/globals.d.ts +0 -6
- package/package.json +1 -1
- package/prebuilds/linux-arm64/node_gi.node +0 -0
- package/prebuilds/linux-x64/node_gi.node +0 -0
- package/src/marshal.cc +38 -1
- package/src/object.cc +26 -1
- package/system.js +41 -5
package/gi.d.ts
CHANGED
|
@@ -93,7 +93,7 @@ export interface GObjectNamespace extends GiNamespace {
|
|
|
93
93
|
ParamSpec: ParamSpecFactories;
|
|
94
94
|
/**
|
|
95
95
|
* The FULL introspected `GParamFlags` bitfield (READABLE/WRITABLE/READWRITE/
|
|
96
|
-
* CONSTRUCT/CONSTRUCT_ONLY/LAX_VALIDATION/STATIC_
|
|
96
|
+
* CONSTRUCT/CONSTRUCT_ONLY/LAX_VALIDATION/STATIC_.../EXPLICIT_NOTIFY/DEPRECATED/
|
|
97
97
|
* …) — the named members below are the common convenience bits; the index
|
|
98
98
|
* signature covers the remaining introspected members.
|
|
99
99
|
*/
|
package/globals.d.ts
CHANGED
|
@@ -32,17 +32,11 @@ export interface GjsImports {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
declare global {
|
|
35
|
-
// eslint-disable-next-line no-var
|
|
36
35
|
var print: (...args: unknown[]) => void;
|
|
37
|
-
// eslint-disable-next-line no-var
|
|
38
36
|
var printerr: (...args: unknown[]) => void;
|
|
39
|
-
// eslint-disable-next-line no-var
|
|
40
37
|
var log: (...args: unknown[]) => void;
|
|
41
|
-
// eslint-disable-next-line no-var
|
|
42
38
|
var logError: (error: unknown, prefix?: string) => void;
|
|
43
|
-
// eslint-disable-next-line no-var
|
|
44
39
|
var ARGV: string[];
|
|
45
|
-
// eslint-disable-next-line no-var
|
|
46
40
|
var imports: GjsImports;
|
|
47
41
|
}
|
|
48
42
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjsify/node-gi",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.24.2",
|
|
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
|
package/src/marshal.cc
CHANGED
|
@@ -153,6 +153,43 @@ BoxedHandle* TryGetBoxedHandle(Napi::Value v) {
|
|
|
153
153
|
return ext.Data();
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
+
// The pointer to hand a callee for a boxed/struct IN argument, honouring the
|
|
157
|
+
// arg's TRANSFER annotation.
|
|
158
|
+
//
|
|
159
|
+
// A transfer-full (or -container) IN arg is ADOPTED by the callee: it will free
|
|
160
|
+
// the pointer. Handing over the very block this handle still owns makes both
|
|
161
|
+
// sides free it — the finalizer's g_boxed_free then hits already-released
|
|
162
|
+
// memory (`free(): invalid pointer`, SIGABRT). The canonical case is
|
|
163
|
+
// `GstWebRTC.WebRTCSessionDescription.new(type, sdp)`, whose `sdp` param is
|
|
164
|
+
// `(transfer full)`: the fresh `GstSDPMessage` from
|
|
165
|
+
// `GstSdp.SDPMessage.new_from_text()` (an OUT `(transfer full)`, so our handle
|
|
166
|
+
// owns it) got freed once by `gst_webrtc_session_description_free` and again by
|
|
167
|
+
// its own finalizer.
|
|
168
|
+
//
|
|
169
|
+
// gjs solves this by COPYING on transfer — refs/gjs/gi/wrapperutils.h
|
|
170
|
+
// `GIWrapperBase::transfer_to_gi_argument` (`GI_DIRECTION_IN &&
|
|
171
|
+
// transfer != GI_TRANSFER_NOTHING` → `Instance::copy_ptr`), with
|
|
172
|
+
// refs/gjs/gi/struct.h `StructInstance::copy_ptr` = `g_variant_ref` for
|
|
173
|
+
// GVariant else refs/gjs/gi/boxed.h `BoxedInstance::copy_ptr` = `g_boxed_copy`.
|
|
174
|
+
// Mirror that: the callee owns an independent instance, the handle keeps (and
|
|
175
|
+
// still owns) its own. Transfer-NOTHING is a plain borrow — unchanged.
|
|
176
|
+
//
|
|
177
|
+
// A handle with no copy function (a plain, non-boxed C struct — G_TYPE_INVALID,
|
|
178
|
+
// or a `rawOwned` caller-allocates OUT block) cannot be duplicated. gjs throws
|
|
179
|
+
// there; we instead RELINQUISH ownership so the finalizer stays out of the
|
|
180
|
+
// callee's way — a leak-free hand-over is impossible either way, and not
|
|
181
|
+
// double-freeing is strictly safer than the alternative.
|
|
182
|
+
gpointer TransferBoxedIn(BoxedHandle* h, GITransfer transfer) {
|
|
183
|
+
if (h == nullptr) return nullptr;
|
|
184
|
+
if (transfer == GI_TRANSFER_NOTHING || h->ptr == nullptr) return h->ptr;
|
|
185
|
+
if (h->gtype == G_TYPE_VARIANT) return g_variant_ref(static_cast<GVariant*>(h->ptr));
|
|
186
|
+
if (h->gtype != G_TYPE_INVALID && G_TYPE_IS_BOXED(h->gtype))
|
|
187
|
+
return g_boxed_copy(h->gtype, h->ptr);
|
|
188
|
+
h->owns = false;
|
|
189
|
+
h->rawOwned = false;
|
|
190
|
+
return h->ptr;
|
|
191
|
+
}
|
|
192
|
+
|
|
156
193
|
// Read a boxed handle's pointer if `v` is one (tag-checked; no deref of ptr).
|
|
157
194
|
bool TryGetBoxedPtr(Napi::Value v, gpointer* out) {
|
|
158
195
|
if (!v.IsExternal()) return false;
|
|
@@ -440,7 +477,7 @@ bool JsToGIArgument(Napi::Env env, Napi::Value v, GITypeInfo* type, GIArgument*
|
|
|
440
477
|
.ThrowAsJavaScriptException();
|
|
441
478
|
return false;
|
|
442
479
|
}
|
|
443
|
-
out->v_pointer = h
|
|
480
|
+
out->v_pointer = TransferBoxedIn(h, transfer);
|
|
444
481
|
handled = true;
|
|
445
482
|
}
|
|
446
483
|
}
|
package/src/object.cc
CHANGED
|
@@ -258,7 +258,32 @@ Napi::Value GValueToJs(Napi::Env env, const GValue* v) {
|
|
|
258
258
|
// NOT special-cased here: gjs leaves it as a GLib.Bytes boxed handle
|
|
259
259
|
// (value.cpp has no GBytes case → the generic boxed branch), which is what the
|
|
260
260
|
// MakeBoxedHandle below already produces (verified: a GBytes signal param →
|
|
261
|
-
// `instanceof GLib.Bytes`).
|
|
261
|
+
// `instanceof GLib.Bytes`).
|
|
262
|
+
//
|
|
263
|
+
// G_TYPE_STRV (a NULL-terminated gchar**, e.g. Gtk.Widget:css-classes,
|
|
264
|
+
// Gio.ThemedIcon:names, or any Vala `public string[] x { get; }` property —
|
|
265
|
+
// valac emits g_param_spec_boxed(…, G_TYPE_STRV) for those). This is the READ
|
|
266
|
+
// half of the pair JsToGValue's GStrv branch opened: without it a GStrv
|
|
267
|
+
// property fell through to MakeBoxedHandle and JS got an opaque boxed handle
|
|
268
|
+
// instead of an array — SILENTLY, because a handle is a truthy object whose
|
|
269
|
+
// `.length` is undefined, so `arr ?? []` keeps it and every index/`for` over it
|
|
270
|
+
// yields nothing (this is how @gjsify/http lost every request header on the
|
|
271
|
+
// node-gi bridge: `bridgeReq.header_pairs` became a handle and the pair loop
|
|
272
|
+
// ran zero times). Mirrors GJS gjs_value_from_g_value_internal
|
|
273
|
+
// (refs/gjs/gi/value.cpp:1082 `gtype == G_TYPE_STRV` → gjs_array_from_strv).
|
|
274
|
+
// NULL → an EMPTY ARRAY, not null: GJS explicitly excludes G_TYPE_STRV from its
|
|
275
|
+
// "pointer-valued NULL → JS null" pre-check (value.cpp:1023) and documents the
|
|
276
|
+
// choice in gjs_array_from_strv (arg.cpp:2267) — "clients would need to always
|
|
277
|
+
// check for both an empty array and null" otherwise. g_value_get_boxed BORROWS;
|
|
278
|
+
// every string is copied into JS, so nothing is owned here.
|
|
279
|
+
if (G_VALUE_HOLDS(v, G_TYPE_STRV)) {
|
|
280
|
+
const gchar* const* strv = static_cast<const gchar* const*>(g_value_get_boxed(v));
|
|
281
|
+
guint len = 0;
|
|
282
|
+
while (strv != nullptr && strv[len] != nullptr) len++;
|
|
283
|
+
Napi::Array arr = Napi::Array::New(env, len);
|
|
284
|
+
for (guint i = 0; i < len; i++) arr.Set(i, Napi::String::New(env, strv[i]));
|
|
285
|
+
return arr;
|
|
286
|
+
}
|
|
262
287
|
if (G_VALUE_HOLDS(v, G_TYPE_BYTE_ARRAY)) {
|
|
263
288
|
GByteArray* ba = static_cast<GByteArray*>(g_value_get_boxed(v));
|
|
264
289
|
if (ba == nullptr) return env.Null();
|
package/system.js
CHANGED
|
@@ -20,25 +20,61 @@
|
|
|
20
20
|
// returning the same shape GJS does — they exist so GJS source that calls them
|
|
21
21
|
// keeps running unmodified, not to reproduce SpiderMonkey internals.
|
|
22
22
|
|
|
23
|
+
// The RUNTIME's own `process`, bound ONCE at module evaluation.
|
|
24
|
+
//
|
|
25
|
+
// `globalThis.process` is NOT a safe source for this module: a GJS program run
|
|
26
|
+
// through the reverse bridge routinely installs the gjsify `@gjsify/process`
|
|
27
|
+
// polyfill over it (`@gjsify/node-globals/register` defines it unconditionally),
|
|
28
|
+
// and that polyfill's `exit()` takes its GJS path whenever `imports.system.exit`
|
|
29
|
+
// exists — i.e. it delegates back into THIS module. Reading `globalThis.process`
|
|
30
|
+
// in `exit()` closes that loop into an infinite, GLib-idle-driven recursion
|
|
31
|
+
// (exit → polyfill → system.exit → polyfill → …) that spins at 100 % CPU and
|
|
32
|
+
// never terminates. `getBuiltinModule('node:process')` (Node ≥ 22.3 / Bun /
|
|
33
|
+
// Deno 2) returns the real builtin regardless of what the global currently is;
|
|
34
|
+
// the module-evaluation capture is the fallback for hosts without it — this
|
|
35
|
+
// module is loaded from `@gjsify/node-gi/globals`, before any bundle body (and
|
|
36
|
+
// therefore before any register side effect) runs.
|
|
37
|
+
const runtimeProcess = (() => {
|
|
38
|
+
const hostProcess = typeof process !== 'undefined' ? process : undefined;
|
|
39
|
+
try {
|
|
40
|
+
const builtin = hostProcess?.getBuiltinModule?.('node:process');
|
|
41
|
+
if (builtin && typeof builtin.exit === 'function') return builtin;
|
|
42
|
+
} catch {
|
|
43
|
+
// getBuiltinModule missing or refusing the specifier — use the captured one.
|
|
44
|
+
}
|
|
45
|
+
return hostProcess;
|
|
46
|
+
})();
|
|
47
|
+
|
|
48
|
+
const runtimeExit = typeof runtimeProcess?.exit === 'function' ? runtimeProcess.exit.bind(runtimeProcess) : undefined;
|
|
49
|
+
|
|
23
50
|
// `programArgs` mirrors GJS's ARGV: the script arguments, excluding the
|
|
24
51
|
// interpreter (argv[0]) and the script path (argv[1]).
|
|
25
52
|
function readProgramArgs() {
|
|
26
|
-
return
|
|
53
|
+
return Array.isArray(runtimeProcess?.argv) ? runtimeProcess.argv.slice(2) : [];
|
|
27
54
|
}
|
|
28
55
|
|
|
29
56
|
// `programInvocationName` / `programPath` track the running script — Node's
|
|
30
57
|
// `process.argv[1]`.
|
|
31
58
|
function readProgramInvocationName() {
|
|
32
|
-
return
|
|
59
|
+
return runtimeProcess?.argv?.[1] || '';
|
|
33
60
|
}
|
|
34
61
|
|
|
35
62
|
function readProgramPath() {
|
|
36
|
-
return
|
|
63
|
+
return runtimeProcess?.argv?.[1] || null;
|
|
37
64
|
}
|
|
38
65
|
|
|
39
|
-
/**
|
|
66
|
+
/**
|
|
67
|
+
* Exit the process (GJS `System.exit`).
|
|
68
|
+
*
|
|
69
|
+
* Terminal by contract: under gjs this is the `exit()` syscall — it never
|
|
70
|
+
* returns and nothing a program installs can intercept it. The Node/Bun/Deno
|
|
71
|
+
* equivalent is the RUNTIME's own `process.exit`, resolved above; routing
|
|
72
|
+
* through `globalThis.process` instead is what produced the never-exiting
|
|
73
|
+
* reverse-bridge process (see the `runtimeProcess` note and
|
|
74
|
+
* `conformance/programs/system-exit-lifetime.conf.mjs`).
|
|
75
|
+
*/
|
|
40
76
|
export function exit(code) {
|
|
41
|
-
if (
|
|
77
|
+
if (runtimeExit !== undefined) runtimeExit(code ?? 0);
|
|
42
78
|
}
|
|
43
79
|
|
|
44
80
|
/** Trigger a garbage collection if the host exposed `globalThis.gc` (`--expose-gc`). */
|