@gjsify/node-gi 0.59.1 → 0.61.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/gi.d.ts +10 -0
- package/gi.js +114 -1
- package/host.nativescript.js +60 -4
- 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/marshal.cc +34 -0
- package/src/signals.cc +14 -1
package/gi.d.ts
CHANGED
|
@@ -138,6 +138,16 @@ export interface GObjectNamespace extends GiNamespace {
|
|
|
138
138
|
*/
|
|
139
139
|
registerClass(klass: Function): Function;
|
|
140
140
|
registerClass(meta: RegisterClassMeta, klass: Function): Function;
|
|
141
|
+
/**
|
|
142
|
+
* Static-field key for {@link RegisterClassMeta.GTypeName} — gjs's meta FIELD form,
|
|
143
|
+
* `Klass[GObject.GTypeName] = 'X'` instead of a meta object. A meta object passed to
|
|
144
|
+
* `registerClass` still wins over the field.
|
|
145
|
+
*/
|
|
146
|
+
GTypeName: symbol;
|
|
147
|
+
/** Static-field key for {@link RegisterClassMeta.Properties} (the field form of the meta). */
|
|
148
|
+
properties: symbol;
|
|
149
|
+
/** Static-field key for {@link RegisterClassMeta.Signals} (the field form of the meta). */
|
|
150
|
+
signals: symbol;
|
|
141
151
|
ParamSpec: ParamSpecFactories;
|
|
142
152
|
/**
|
|
143
153
|
* The FULL introspected `GParamFlags` bitfield (READABLE/WRITABLE/READWRITE/
|
package/gi.js
CHANGED
|
@@ -809,6 +809,12 @@ function handlerIdsForFunc(handle, fn) {
|
|
|
809
809
|
// routing keys on the leaf so construction is correct at any depth.
|
|
810
810
|
const registeredClasses = new Map();
|
|
811
811
|
|
|
812
|
+
// A registered class's prototype → the kebab names of the CUSTOM GObject properties
|
|
813
|
+
// it declared, plus every registered ancestor's. Keyed by the prototype because that
|
|
814
|
+
// is what an instance carries (USER_PROTO), and the set trap is the only consumer:
|
|
815
|
+
// see the equal-value guard in wrapInstance.
|
|
816
|
+
const customPropertiesByProto = new WeakMap();
|
|
817
|
+
|
|
812
818
|
// GTypeName → registered JS class, the reverse of registeredClasses. The engine's
|
|
813
819
|
// NodeGiConstructor hands runCtorForCObject (below) the C-created instance's GType
|
|
814
820
|
// NAME (g_type_name), which this resolves back to the class to Reflect.construct.
|
|
@@ -1309,6 +1315,33 @@ function wrapInstance(handle, userProto) {
|
|
|
1309
1315
|
const target = { [HANDLE]: handle };
|
|
1310
1316
|
const proto = userProto ?? protoForInstance(handle);
|
|
1311
1317
|
if (proto !== undefined) linkInstanceProto(target, proto);
|
|
1318
|
+
// Which CUSTOM properties of this instance have a value yet — the thing GJS's
|
|
1319
|
+
// generated accessors track with the PRESENCE of their private field, and what its
|
|
1320
|
+
// setter's equal-value guard actually compares against (`value !== this[privateName]`,
|
|
1321
|
+
// refs/gjs/modules/core/_common.js). Not the ParamSpec default: on gjs 1.88.1 a fresh
|
|
1322
|
+
// object's `o.p = <default>` DOES notify (the field holds `undefined`), while the same
|
|
1323
|
+
// assignment after a READ does not — the getter installs the default into the field
|
|
1324
|
+
// first (`if (!(privateName in this)) this[privateName] = defaultValue`) — and nor
|
|
1325
|
+
// does it after `new K({p: <default>})`, since the construct value went through the
|
|
1326
|
+
// setter. Three behaviours, one state: has the value been materialised.
|
|
1327
|
+
//
|
|
1328
|
+
// Seeded lazily from what the ENGINE has stored (`storedPropertyNames` — construct
|
|
1329
|
+
// values included), then extended by every read and write through this wrapper. A set
|
|
1330
|
+
// from C on a property no JS code has touched is the one gap, and it falls back to
|
|
1331
|
+
// notifying — what node-gi did before the guard existed. Same for an addon too old to
|
|
1332
|
+
// export storedPropertyNames (the cross-version pairing of the shipped-closure legs,
|
|
1333
|
+
// see runJsPropertySetter): the guard then only narrows, never widens.
|
|
1334
|
+
let materialised;
|
|
1335
|
+
const materialise = (name) => {
|
|
1336
|
+
if (materialised === undefined) {
|
|
1337
|
+
materialised = new Set(
|
|
1338
|
+
typeof native.storedPropertyNames === 'function' ? native.storedPropertyNames(handle) : [],
|
|
1339
|
+
);
|
|
1340
|
+
}
|
|
1341
|
+
const had = materialised.has(name);
|
|
1342
|
+
materialised.add(name);
|
|
1343
|
+
return had;
|
|
1344
|
+
};
|
|
1312
1345
|
const proxy = new Proxy(target, {
|
|
1313
1346
|
get(t, prop) {
|
|
1314
1347
|
if (prop === HANDLE) return handle;
|
|
@@ -1400,6 +1433,14 @@ function wrapInstance(handle, userProto) {
|
|
|
1400
1433
|
if (shim !== undefined) return (...args) => shim(handle, args);
|
|
1401
1434
|
const propName = toKebab(prop);
|
|
1402
1435
|
if (native.hasProperty(handle, propName)) {
|
|
1436
|
+
// A READ materialises a custom property's value, exactly as GJS's generated
|
|
1437
|
+
// getter writes the default into its private field on first look-up — which
|
|
1438
|
+
// is what makes a LATER assignment of that same default silent. See the
|
|
1439
|
+
// equal-value guard in the set trap.
|
|
1440
|
+
const up = t[USER_PROTO];
|
|
1441
|
+
if (up !== undefined && customPropertiesByProto.get(up)?.has(propName) === true) {
|
|
1442
|
+
materialise(propName);
|
|
1443
|
+
}
|
|
1403
1444
|
return wrapReturn(native.getProperty(handle, propName));
|
|
1404
1445
|
}
|
|
1405
1446
|
// LAST resort before treating an unknown name as a GI method: an INHERITED
|
|
@@ -1444,6 +1485,29 @@ function wrapInstance(handle, userProto) {
|
|
|
1444
1485
|
}
|
|
1445
1486
|
const propName = toKebab(prop);
|
|
1446
1487
|
if (native.hasProperty(handle, propName)) {
|
|
1488
|
+
// Equal-value guard, GJS's `_generateAccessors` setter verbatim:
|
|
1489
|
+
// `if (value !== this[privateName]) { …; this.notify(name) }`
|
|
1490
|
+
// (refs/gjs/modules/core/_common.js). GJS puts it in the accessor it
|
|
1491
|
+
// GENERATES for a declared property, so it covers exactly the custom
|
|
1492
|
+
// properties with no accessor of the class's own — the branch above
|
|
1493
|
+
// already took every class-declared accessor, and `set_property`
|
|
1494
|
+
// bypasses the accessor and so keeps notifying unconditionally on both
|
|
1495
|
+
// runtimes (measured on gjs 1.88.1: assignment [1,1,2], set_property
|
|
1496
|
+
// [1,2,3]). node-gi installs no generated accessors (the engine store
|
|
1497
|
+
// IS the backing store, see findPropertySetter), so without this the
|
|
1498
|
+
// store's unconditional g_object_notify_by_pspec made every assignment
|
|
1499
|
+
// notify — [1,2,3] where gjs gives [1,1,2], and the three spellings of
|
|
1500
|
+
// a dashed name notified once EACH instead of sharing one.
|
|
1501
|
+
//
|
|
1502
|
+
// Only once the value is MATERIALISED (see `materialise`): an unset
|
|
1503
|
+
// property's private field is `undefined` on gjs, never the ParamSpec
|
|
1504
|
+
// default, so the first assignment notifies even when it equals the
|
|
1505
|
+
// default. The dash spelling is the key, since toKebab canonicalises
|
|
1506
|
+
// all three spellings onto it.
|
|
1507
|
+
const custom = up !== undefined && customPropertiesByProto.get(up)?.has(propName) === true;
|
|
1508
|
+
if (custom && materialise(propName)) {
|
|
1509
|
+
if (wrapReturn(native.getProperty(handle, propName)) === value) return true;
|
|
1510
|
+
}
|
|
1447
1511
|
native.setProperty(handle, propName, unwrapArg(value));
|
|
1448
1512
|
return true;
|
|
1449
1513
|
}
|
|
@@ -1695,10 +1759,17 @@ function makeClass(namespace, typeName) {
|
|
|
1695
1759
|
handle = native.constructType(reg.typeHandle, props ? unwrapProps(props) : {});
|
|
1696
1760
|
}
|
|
1697
1761
|
const instance = wrapInstance(handle, nt.prototype);
|
|
1698
|
-
assignTemplateChildren(instance, handle, reg);
|
|
1699
1762
|
// Route construct-time property values through the class's JS setters now
|
|
1700
1763
|
// that USER_PROTO is attached — before the user ctor body runs (GJS order).
|
|
1764
|
+
//
|
|
1765
|
+
// BEFORE the template children, because that is the order GJS produces: a
|
|
1766
|
+
// construct property's setter runs inside g_object_new, where the template
|
|
1767
|
+
// is not yet built, so it sees `this._child === undefined`; the children
|
|
1768
|
+
// appear only once construction returns. Binding them first made a setter
|
|
1769
|
+
// observe a child GJS cannot have handed it yet, so a class that guards on
|
|
1770
|
+
// `if (this._child)` took the wrong branch on node-gi and only on node-gi.
|
|
1701
1771
|
flushPropertiesToJsSetters(instance, handle, reg);
|
|
1772
|
+
assignTemplateChildren(instance, handle, reg);
|
|
1702
1773
|
return instance;
|
|
1703
1774
|
}
|
|
1704
1775
|
// `nt` is a subclass of this introspected GObject class but was NEVER passed to
|
|
@@ -2349,6 +2420,19 @@ function collectVfuncs(klass) {
|
|
|
2349
2420
|
* @param {Function} [maybeClass]
|
|
2350
2421
|
* @returns {Function} the same `klass` (now registered)
|
|
2351
2422
|
*/
|
|
2423
|
+
// GJS's meta FIELD form: a class may declare its meta as static symbol fields
|
|
2424
|
+
// (`Klass[GObject.GTypeName] = 'X'`) instead of passing a meta object, and GJS treats
|
|
2425
|
+
// the symbols as the real source — `registerClass(meta, klass)` only copies meta onto
|
|
2426
|
+
// them and reads them back (refs/gjs/modules/core/_common.js). The descriptions match
|
|
2427
|
+
// GJS's so a stringified symbol reads the same in both runtimes.
|
|
2428
|
+
//
|
|
2429
|
+
// GObject.interfaces / GObject.interface requires are deliberately absent: registerClass
|
|
2430
|
+
// implements no interfaces in EITHER form yet, and a symbol that is accepted and then
|
|
2431
|
+
// ignored promises more than the object form delivers.
|
|
2432
|
+
const GTypeNameSymbol = Symbol('GType name');
|
|
2433
|
+
const propertiesSymbol = Symbol('GObject properties');
|
|
2434
|
+
const signalsSymbol = Symbol('GObject signals');
|
|
2435
|
+
|
|
2352
2436
|
function registerClass(metaOrClass, maybeClass) {
|
|
2353
2437
|
let meta;
|
|
2354
2438
|
let klass;
|
|
@@ -2363,6 +2447,17 @@ function registerClass(metaOrClass, maybeClass) {
|
|
|
2363
2447
|
throw new TypeError('GObject.registerClass: expected a class to register');
|
|
2364
2448
|
}
|
|
2365
2449
|
|
|
2450
|
+
// Fold in whatever the class declared as symbol fields, with the meta OBJECT winning
|
|
2451
|
+
// — the same precedence GJS gets from copying meta onto the symbols before reading
|
|
2452
|
+
// them. Until the symbols existed on the namespace, `{[G.GTypeName]: 'X'}` wrote a
|
|
2453
|
+
// literal "undefined" key and the whole declaration vanished without a word: the
|
|
2454
|
+
// GTypeName fell back to the class name and the signals were never registered.
|
|
2455
|
+
const fieldMeta = {};
|
|
2456
|
+
if (klass[GTypeNameSymbol] !== undefined) fieldMeta.GTypeName = klass[GTypeNameSymbol];
|
|
2457
|
+
if (klass[propertiesSymbol] !== undefined) fieldMeta.Properties = klass[propertiesSymbol];
|
|
2458
|
+
if (klass[signalsSymbol] !== undefined) fieldMeta.Signals = klass[signalsSymbol];
|
|
2459
|
+
meta = { ...fieldMeta, ...meta };
|
|
2460
|
+
|
|
2366
2461
|
const parent = findParentGType(klass);
|
|
2367
2462
|
if (parent === undefined) {
|
|
2368
2463
|
throw new TypeError(
|
|
@@ -2456,6 +2551,18 @@ function registerClass(metaOrClass, maybeClass) {
|
|
|
2456
2551
|
internalChildren: internalChildren.length > 0 ? internalChildren : undefined,
|
|
2457
2552
|
constructPropertyNames: constructPropertyNames.length > 0 ? constructPropertyNames : undefined,
|
|
2458
2553
|
});
|
|
2554
|
+
// Which properties the equal-value guard owns: the ones DECLARED here, union the
|
|
2555
|
+
// registered parent's (a G2 subclass inherits its parent's custom properties and
|
|
2556
|
+
// the guard must follow them down). Introspected properties are deliberately
|
|
2557
|
+
// absent — their C setter decides whether an equal value notifies, and guarding
|
|
2558
|
+
// them here would answer for it.
|
|
2559
|
+
const inheritedCustom = registeredClasses.get(Object.getPrototypeOf(klass))?.customProperties;
|
|
2560
|
+
const customProperties = new Set(inheritedCustom);
|
|
2561
|
+
for (const p of properties) {
|
|
2562
|
+
if (typeof p.name === 'string') customProperties.add(p.name);
|
|
2563
|
+
}
|
|
2564
|
+
registeredClasses.get(klass).customProperties = customProperties;
|
|
2565
|
+
customPropertiesByProto.set(klass.prototype, customProperties);
|
|
2459
2566
|
// Reverse index for runCtorForCObject: the engine identifies a C-created instance
|
|
2460
2567
|
// by its GType name (= gtypeName, what native.registerClass registered).
|
|
2461
2568
|
classesByGType.set(gtypeName, klass);
|
|
@@ -2551,6 +2658,9 @@ function signalEmitByName(object, ...nameAndArgs) {
|
|
|
2551
2658
|
// makeClass). Merged enums are cached so identity is stable.
|
|
2552
2659
|
const OVERLAY_NAMES = new Set([
|
|
2553
2660
|
'registerClass',
|
|
2661
|
+
'GTypeName',
|
|
2662
|
+
'properties',
|
|
2663
|
+
'signals',
|
|
2554
2664
|
'ParamSpec',
|
|
2555
2665
|
'ParamFlags',
|
|
2556
2666
|
'SignalFlags',
|
|
@@ -2617,6 +2727,9 @@ function decorateGObjectNamespace(baseNs) {
|
|
|
2617
2727
|
if (cache.has(prop)) return cache.get(prop);
|
|
2618
2728
|
let value;
|
|
2619
2729
|
if (prop === 'registerClass') value = registerClass;
|
|
2730
|
+
else if (prop === 'GTypeName') value = GTypeNameSymbol;
|
|
2731
|
+
else if (prop === 'properties') value = propertiesSymbol;
|
|
2732
|
+
else if (prop === 'signals') value = signalsSymbol;
|
|
2620
2733
|
else if (prop === 'ParamSpec') value = ParamSpec;
|
|
2621
2734
|
else if (prop === 'ParamFlags') value = mergeFlags(baseNs.ParamFlags, ParamFlags);
|
|
2622
2735
|
else if (prop === 'SignalFlags') value = mergeFlags(baseNs.SignalFlags, SignalFlags);
|
package/host.nativescript.js
CHANGED
|
@@ -157,18 +157,74 @@ function configureEnvironment() {
|
|
|
157
157
|
setNativeEnv('XDG_RUNTIME_DIR', `${appDir}/.run`);
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
+
/**
|
|
161
|
+
* The names under which NativeScript's own module loader can be reached from the GLOBAL scope,
|
|
162
|
+
* in the order a bundle may offer them. `system_lib://` is the runtime's scheme, so whichever
|
|
163
|
+
* of these answers, it is the same loader underneath.
|
|
164
|
+
*
|
|
165
|
+
* * `require` — the loader the runtime installs globally. A CommonJS bundle also has a
|
|
166
|
+
* module-scoped `require`, but that one is the BUNDLER's; reading the global by name is
|
|
167
|
+
* what keeps the two apart, and it is why this list is read off a scope object rather
|
|
168
|
+
* than written as bare identifiers.
|
|
169
|
+
* * `__nativeRequire` — the same loader under the name the runtime uses internally
|
|
170
|
+
* (measured in @nativescript/android 9.1's libNativeScript.so, beside `ns:module`'s
|
|
171
|
+
* `createRequire`). It is the fallback for a runtime that stops exposing `require`
|
|
172
|
+
* globally, which an ESM-only bundle would otherwise have no way to reach.
|
|
173
|
+
*/
|
|
174
|
+
const RUNTIME_REQUIRE_GLOBALS = ['require', '__nativeRequire'];
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Pick the function that can load `system_lib://libnode_gi.so`.
|
|
178
|
+
*
|
|
179
|
+
* WHY THIS IS A CHAIN AND NOT webpack's ONE NAME. `__non_webpack_require__` only exists
|
|
180
|
+
* because webpack REWRITES that free identifier to the runtime's require; under
|
|
181
|
+
* @nativescript/vite (Rolldown, ESM output) nothing rewrites it, so the old single check made
|
|
182
|
+
* every non-webpack NativeScript bundle fail at the first `requireGi` with a message telling
|
|
183
|
+
* the app to switch bundlers — which is not a fix available to a vite app.
|
|
184
|
+
*
|
|
185
|
+
* ORDER MATTERS. webpack's rewrite wins first: in a webpack bundle the module-scoped `require`
|
|
186
|
+
* is webpack's own and would resolve `system_lib://…` against the bundle graph instead of the
|
|
187
|
+
* runtime. Only after that do the runtime's global names get a turn.
|
|
188
|
+
*
|
|
189
|
+
* Pure on purpose — `scope` is an argument so the chain is testable without a device
|
|
190
|
+
* (`test/host-nativescript.test.mjs`).
|
|
191
|
+
*
|
|
192
|
+
* @param {Record<string, unknown>} scope the global object to read the runtime names off
|
|
193
|
+
* @param {unknown} webpackRequire webpack's rewritten `__non_webpack_require__`, if any
|
|
194
|
+
* @returns {{ load: ((id: string) => any) | null, via: string | null }}
|
|
195
|
+
*/
|
|
196
|
+
export function resolveNativeRequire(scope, webpackRequire) {
|
|
197
|
+
if (typeof webpackRequire === 'function') {
|
|
198
|
+
return { load: /** @type {(id: string) => any} */ (webpackRequire), via: '__non_webpack_require__' };
|
|
199
|
+
}
|
|
200
|
+
for (const name of RUNTIME_REQUIRE_GLOBALS) {
|
|
201
|
+
const candidate = scope?.[name];
|
|
202
|
+
if (typeof candidate === 'function') {
|
|
203
|
+
return { load: /** @type {(id: string) => any} */ (candidate), via: name };
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return { load: null, via: null };
|
|
207
|
+
}
|
|
208
|
+
|
|
160
209
|
/**
|
|
161
210
|
* Load the addon from the APK's native libs and point GI at the app's typelibs.
|
|
162
211
|
* @returns {Record<string, any>} the native engine
|
|
163
212
|
*/
|
|
164
213
|
export function loadNativeHost() {
|
|
165
214
|
configureEnvironment();
|
|
166
|
-
// webpack rewrites
|
|
167
|
-
|
|
215
|
+
// The bare identifier is what webpack rewrites; everything else is read off the global
|
|
216
|
+
// scope inside resolveNativeRequire.
|
|
217
|
+
const { load } = resolveNativeRequire(
|
|
218
|
+
globalThis,
|
|
219
|
+
typeof __non_webpack_require__ === 'function' ? __non_webpack_require__ : undefined,
|
|
220
|
+
);
|
|
168
221
|
if (load === null) {
|
|
169
222
|
throw new Error(
|
|
170
|
-
'@gjsify/node-gi: the NativeScript host
|
|
171
|
-
'
|
|
223
|
+
'@gjsify/node-gi: the NativeScript host found no module loader to pass ' +
|
|
224
|
+
"'system_lib://libnode_gi.so' to. It looked for __non_webpack_require__ (webpack " +
|
|
225
|
+
`rewrites it) and then for ${RUNTIME_REQUIRE_GLOBALS.map((n) => `globalThis.${n}`).join(' and ')}` +
|
|
226
|
+
" — the names NativeScript's own loader answers to. None of them is a function, which " +
|
|
227
|
+
'means this is not a NativeScript runtime, or its loader moved.',
|
|
172
228
|
);
|
|
173
229
|
}
|
|
174
230
|
const native = load('system_lib://libnode_gi.so');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjsify/node-gi",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.61.0",
|
|
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/marshal.cc
CHANGED
|
@@ -605,6 +605,40 @@ bool JsToGIArgument(Napi::Env env, Napi::Value v, GITypeInfo* type, GIArgument*
|
|
|
605
605
|
out->v_string = const_cast<char*>(heldString->c_str());
|
|
606
606
|
}
|
|
607
607
|
return true;
|
|
608
|
+
case GI_TYPE_TAG_VOID:
|
|
609
|
+
// A bare `gpointer` IN argument: the `user_data` of the log handlers
|
|
610
|
+
// (`GLib.log_default_handler(domain, level, message, null)`), of
|
|
611
|
+
// `g_log_writer_default`, and of every introspected function that takes a
|
|
612
|
+
// closure's data separately. There is nothing to marshal INTO a void* from
|
|
613
|
+
// JS, so gjs installs Arg::NullIn for this tag (refs/gjs/gi/arg-cache.cpp
|
|
614
|
+
// ArgsCache::build_normal_in_arg) — it writes NULL and ignores the value
|
|
615
|
+
// entirely. Without this arm the whole CALL failed with "Unsupported IN
|
|
616
|
+
// argument type tag 0", which is how `GLib.log_default_handler` — the only
|
|
617
|
+
// introspectable way to make GLib write to fd 1/2, and so the only way to
|
|
618
|
+
// SEE the Android stdout/stderr redirect from JS — was unreachable.
|
|
619
|
+
//
|
|
620
|
+
// Deliberate divergence: a value that is not null/undefined is REFUSED here
|
|
621
|
+
// where gjs drops it silently. node-gi cannot turn a JS value into a
|
|
622
|
+
// gpointer, and a caller that passes one meant it to arrive; swallowing it
|
|
623
|
+
// hides the mistake at the only place that can still name the argument.
|
|
624
|
+
// (The nullability of the arg is not known at this level — JsToGIArgument
|
|
625
|
+
// sees the GITypeInfo, not the GIArgInfo — so a non-nullable void* gets
|
|
626
|
+
// NULL instead of gjs's "argument may not be null". In the introspected API
|
|
627
|
+
// such an argument is `allow-none` throughout.)
|
|
628
|
+
if (!gi_type_info_is_pointer(type)) return true; // plain `void`: no slot to fill
|
|
629
|
+
if (v.IsNull() || v.IsUndefined()) {
|
|
630
|
+
out->v_pointer = nullptr;
|
|
631
|
+
return true;
|
|
632
|
+
}
|
|
633
|
+
{
|
|
634
|
+
std::string msg = std::string("Expected null for pointer ") +
|
|
635
|
+
(argName != nullptr ? std::string("argument '") + argName + "'"
|
|
636
|
+
: std::string("argument")) +
|
|
637
|
+
" but got type " + InformalValueTypeName(v) +
|
|
638
|
+
" (node-gi cannot marshal a JS value into a gpointer)";
|
|
639
|
+
Napi::TypeError::New(env, msg).ThrowAsJavaScriptException();
|
|
640
|
+
}
|
|
641
|
+
return false;
|
|
608
642
|
case GI_TYPE_TAG_INTERFACE: {
|
|
609
643
|
// Object/interface instances arrive as opaque External<GObject> handles;
|
|
610
644
|
// enums/flags as plain numbers. Other interface kinds (structs/unions/
|
package/src/signals.cc
CHANGED
|
@@ -270,6 +270,19 @@ Napi::Value EmitSignal(const Napi::CallbackInfo& info) {
|
|
|
270
270
|
g_signal_query(sigid, &query);
|
|
271
271
|
|
|
272
272
|
guint n = query.n_params;
|
|
273
|
+
// ARITY, gjs's message verbatim ("Signal 'changed' on ArSig requires 2 args got 1",
|
|
274
|
+
// measured on gjs 1.88.1 — which rejects too MANY as well as too few). Without it a
|
|
275
|
+
// missing argument was marshalled from `undefined`: a signal declared (string, int)
|
|
276
|
+
// and emitted with one argument reached its handlers with a silent "" / 0 in the
|
|
277
|
+
// gap, so the caller's mistake surfaced as wrong data somewhere downstream instead
|
|
278
|
+
// of as a throw at the emit.
|
|
279
|
+
if (args.Length() != n) {
|
|
280
|
+
Napi::Error::New(env, std::string("Signal '") + name + "' on " + G_OBJECT_TYPE_NAME(obj) +
|
|
281
|
+
" requires " + std::to_string(n) + " args got " +
|
|
282
|
+
std::to_string(args.Length()))
|
|
283
|
+
.ThrowAsJavaScriptException();
|
|
284
|
+
return env.Null();
|
|
285
|
+
}
|
|
273
286
|
std::vector<GValue> params(n + 1); // [0] = instance
|
|
274
287
|
g_value_init(¶ms[0], gtype);
|
|
275
288
|
g_value_set_object(¶ms[0], obj);
|
|
@@ -279,7 +292,7 @@ Napi::Value EmitSignal(const Napi::CallbackInfo& info) {
|
|
|
279
292
|
GType pt = query.param_types[i] & ~G_SIGNAL_TYPE_STATIC_SCOPE;
|
|
280
293
|
g_value_init(¶ms[i + 1], pt);
|
|
281
294
|
initialised = i + 2;
|
|
282
|
-
Napi::Value v =
|
|
295
|
+
Napi::Value v = args.Get(i);
|
|
283
296
|
// An EMPTY v is the residue of a swallowed args.Get() failure (terminating
|
|
284
297
|
// env / throwing getter): JsToGValue's coercions on it would abort via
|
|
285
298
|
// Error::New(nullptr)'s fatal sites — bail before marshalling.
|