@gjsify/node-gi 0.64.0 → 0.64.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/cairo.d.ts +63 -0
- package/cairo.js +118 -17
- package/gettext.js +29 -7
- package/gi.js +204 -87
- package/host.nativescript.js +6 -0
- package/host.node.js +6 -0
- package/index.js +192 -76
- 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/cairo.cc +162 -1
- package/src/calls.cc +59 -26
- package/src/class.cc +15 -0
- package/src/common.h +177 -1
- package/src/marshal.cc +77 -26
- package/src/object.cc +79 -10
- package/src/private.cc +33 -0
package/gi.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
// Reference: gjs/modules/esm/gi.js for the require shape; node-gtk (romgrk, MIT) for the
|
|
8
8
|
// binding lineage. Hand-authored JS — the package ships no build step.
|
|
9
9
|
import * as native from './index.js';
|
|
10
|
+
import { ensureNative, onNativeLoaded } from './index.js';
|
|
10
11
|
// Timers, `process` and the windowing env record come from the host seam (host.node.js
|
|
11
12
|
// explains why they are the runtime's own, not the globals; host.nativescript.js is the
|
|
12
13
|
// NativeScript Android variant).
|
|
@@ -48,11 +49,21 @@ function camelToSnake(name) {
|
|
|
48
49
|
return name.replace(/([A-Z])/g, '_$1').toLowerCase();
|
|
49
50
|
}
|
|
50
51
|
|
|
52
|
+
// Memoized: the wrapper's get/set/has traps convert on EVERY property access, the
|
|
53
|
+
// answer depends on nothing but the name, and the set of names a program uses is
|
|
54
|
+
// bounded by its source text — three regex passes per access buy nothing.
|
|
55
|
+
const kebabNames = new Map();
|
|
56
|
+
|
|
51
57
|
function toKebab(name) {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
58
|
+
let kebab = kebabNames.get(name);
|
|
59
|
+
if (kebab === undefined) {
|
|
60
|
+
kebab = name
|
|
61
|
+
.replace(/([A-Z])/g, '-$1')
|
|
62
|
+
.replace(/_/g, '-')
|
|
63
|
+
.toLowerCase();
|
|
64
|
+
kebabNames.set(name, kebab);
|
|
65
|
+
}
|
|
66
|
+
return kebab;
|
|
56
67
|
}
|
|
57
68
|
|
|
58
69
|
// Repeated passes of the same callback must marshal to the SAME native-facing function:
|
|
@@ -221,26 +232,10 @@ function wrapGError(handle, withPosition = false, hideAbove = wrapGError) {
|
|
|
221
232
|
// domain must already name something, so `new GLib.Error()` is refused here rather
|
|
222
233
|
// than producing a GError no `matches()` can ever hit.
|
|
223
234
|
function newErrorLiteral(domain, code, message) {
|
|
224
|
-
|
|
225
|
-
if (native.callFunction('GLib', 'quark_to_string', [quark]) === null) {
|
|
235
|
+
if (native.callFunction('GLib', 'quark_to_string', [domain]) === null) {
|
|
226
236
|
throw new TypeError(`Error.new_literal: ${domain} is not a valid domain`);
|
|
227
237
|
}
|
|
228
|
-
return native.callStaticMethod('GLib', 'Error', 'new_literal', [
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
// GJS's uint32 conversion for a `domain` argument (gi/js-value-inl.h,
|
|
232
|
-
// js_value_to_c_checked): `undefined` and a non-finite NUMBER become 0 silently,
|
|
233
|
-
// anything else goes through ToNumber and is REFUSED when it misses uint32 range.
|
|
234
|
-
// node-gi's own NodeGiToUint32 has no range check, so the node-gi-only spelling
|
|
235
|
-
// `err.matches('g-io-error-quark', code)` converted to NaN→0 and quietly matched
|
|
236
|
-
// nothing; GJS cannot run that line at all, so neither can this.
|
|
237
|
-
function toDomainQuark(domain) {
|
|
238
|
-
if (domain === undefined || (typeof domain === 'number' && !Number.isFinite(domain))) return 0;
|
|
239
|
-
const quark = Number(domain);
|
|
240
|
-
if (Number.isNaN(quark) || quark < 0 || quark > 0xffffffff) {
|
|
241
|
-
throw new Error('Argument domain: value is out of range for uint32');
|
|
242
|
-
}
|
|
243
|
-
return quark >>> 0;
|
|
238
|
+
return native.callStaticMethod('GLib', 'Error', 'new_literal', [domain, code, message]);
|
|
244
239
|
}
|
|
245
240
|
|
|
246
241
|
// `GLib.Error`: a plain FUNCTION, not a `class … extends Error` — GJS's
|
|
@@ -303,7 +298,7 @@ Object.defineProperties(GLibError.prototype, {
|
|
|
303
298
|
`method GLib.Error.matches: At least 2 arguments required, but only ${arguments.length} passed`,
|
|
304
299
|
);
|
|
305
300
|
}
|
|
306
|
-
return native.callBoxedMethod(this[HANDLE], 'matches', [
|
|
301
|
+
return native.callBoxedMethod(this[HANDLE], 'matches', [domain, code]);
|
|
307
302
|
}),
|
|
308
303
|
});
|
|
309
304
|
defineErrorFields(GLibError.prototype, 'GLib.Error', undefined);
|
|
@@ -366,17 +361,19 @@ function makeErrorDomainClass(namespace, typeName, members, quark) {
|
|
|
366
361
|
// Called by the engine on a failed GI invoke, with ownership of the GError* already
|
|
367
362
|
// transferred to the boxed handle. Named, so the position properties report the
|
|
368
363
|
// application frame rather than this one.
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
364
|
+
onNativeLoaded(() =>
|
|
365
|
+
native.setErrorBuilder(function buildThrownGError(handle, quark, code, message) {
|
|
366
|
+
// Same cross-version pairing: an addon older than this JS calls the builder with
|
|
367
|
+
// the GError's FIELDS (domainName, quark, code, message) rather than a handle, and
|
|
368
|
+
// there is no second `errorClass` argument to give it. Rebuilding the GError from
|
|
369
|
+
// the fields keeps the ONE shape on that pairing too — it costs only the original
|
|
370
|
+
// GError's address, which nothing can read.
|
|
371
|
+
if (typeof handle === 'string') {
|
|
372
|
+
return wrapGError(newErrorLiteral(quark, code, message), true, buildThrownGError);
|
|
373
|
+
}
|
|
374
|
+
return wrapGError(handle, true, buildThrownGError);
|
|
375
|
+
}),
|
|
376
|
+
);
|
|
380
377
|
|
|
381
378
|
// Stamps a makeClass prototype with its { namespace, typeName } so Gio._promisify can
|
|
382
379
|
// record WHICH introspected class a registration belongs to — matched against the
|
|
@@ -396,6 +393,13 @@ const promisifiedWrappers = new WeakSet();
|
|
|
396
393
|
const nativeThunks = new WeakSet();
|
|
397
394
|
|
|
398
395
|
function wrapReturn(value) {
|
|
396
|
+
// EVERY engine value comes through here — each return, each OUT tuple element and
|
|
397
|
+
// each signal argument (wrapSignalCallback) — and the five tag probes below are
|
|
398
|
+
// five N-API crossings. A handle is always an OBJECT (a tagged napi External), so a
|
|
399
|
+
// primitive can answer for itself: a `notify::label` handler's `(emitter, pspec)`
|
|
400
|
+
// then costs two probes for the emitter instead of ten for the pair, and the string
|
|
401
|
+
// argument of an ordinary `emit('changed', 'x')` costs none.
|
|
402
|
+
if (value === null || typeof value !== 'object') return value;
|
|
399
403
|
if (native.isGObjectHandle(value)) return wrapInstance(value);
|
|
400
404
|
// A GLib.Variant is ALSO a boxed handle by tag, so it must be checked first to get
|
|
401
405
|
// the Variant ergonomics rather than a plain method-routing proxy.
|
|
@@ -442,7 +446,7 @@ function resolveTemplateCallback(handle, handlerName) {
|
|
|
442
446
|
return desc.value.apply(proxy, args.map(wrapReturn));
|
|
443
447
|
};
|
|
444
448
|
}
|
|
445
|
-
native.setTemplateCallbackResolver(resolveTemplateCallback);
|
|
449
|
+
onNativeLoaded(() => native.setTemplateCallbackResolver(resolveTemplateCallback));
|
|
446
450
|
|
|
447
451
|
// Run a registered class's JS constructor for a GObject the ENGINE instantiated from C
|
|
448
452
|
// — typically a GtkBuilder composite-template InternalChild, which has no JS-`new` to
|
|
@@ -465,7 +469,7 @@ function runCtorForCObject(handle, gtypeName) {
|
|
|
465
469
|
adoptHandle = prev;
|
|
466
470
|
}
|
|
467
471
|
}
|
|
468
|
-
native.setConstructCallback(runCtorForCObject);
|
|
472
|
+
onNativeLoaded(() => native.setConstructCallback(runCtorForCObject));
|
|
469
473
|
|
|
470
474
|
// A non-GObject GObject-fundamental (GskRenderNode, GdkEvent) as an OPAQUE,
|
|
471
475
|
// round-trippable handle: the native tagged External already carries the pointer and
|
|
@@ -543,36 +547,51 @@ function wrapBoxed(handle) {
|
|
|
543
547
|
// GParamSpec-typed value surface as. Carries [HANDLE] so it round-trips back into the
|
|
544
548
|
// engine and `instanceof GObject.ParamSpec` recognises it. value_type/owner_type are
|
|
545
549
|
// native GType handles, like gjs's GType objects.
|
|
550
|
+
// The accessors live HERE, on one shared prototype, and read the handle off `this`.
|
|
551
|
+
// Built per instance they were eleven fresh closures plus a twelve-slot object on
|
|
552
|
+
// EVERY notify emission — the engine hands the pspec to the handler whether or not
|
|
553
|
+
// the handler looks at it, so that allocation landed on every property write of the
|
|
554
|
+
// real-GObject engine. On gjs these same members live on GObject.ParamSpec.prototype,
|
|
555
|
+
// so prototype-held is also the closer shape.
|
|
556
|
+
const paramSpecProto = {
|
|
557
|
+
get name() {
|
|
558
|
+
return native.paramSpecProp(this[HANDLE], 'name');
|
|
559
|
+
},
|
|
560
|
+
get nick() {
|
|
561
|
+
return native.paramSpecProp(this[HANDLE], 'nick');
|
|
562
|
+
},
|
|
563
|
+
get blurb() {
|
|
564
|
+
return native.paramSpecProp(this[HANDLE], 'blurb');
|
|
565
|
+
},
|
|
566
|
+
get flags() {
|
|
567
|
+
return native.paramSpecProp(this[HANDLE], 'flags');
|
|
568
|
+
},
|
|
569
|
+
get value_type() {
|
|
570
|
+
return native.paramSpecProp(this[HANDLE], 'valueType');
|
|
571
|
+
},
|
|
572
|
+
get owner_type() {
|
|
573
|
+
return native.paramSpecProp(this[HANDLE], 'ownerType');
|
|
574
|
+
},
|
|
575
|
+
get default_value() {
|
|
576
|
+
return wrapReturn(native.paramSpecProp(this[HANDLE], 'defaultValue'));
|
|
577
|
+
},
|
|
578
|
+
get_name() {
|
|
579
|
+
return native.paramSpecProp(this[HANDLE], 'name');
|
|
580
|
+
},
|
|
581
|
+
get_nick() {
|
|
582
|
+
return native.paramSpecProp(this[HANDLE], 'nick');
|
|
583
|
+
},
|
|
584
|
+
get_blurb() {
|
|
585
|
+
return native.paramSpecProp(this[HANDLE], 'blurb');
|
|
586
|
+
},
|
|
587
|
+
get_default_value() {
|
|
588
|
+
return wrapReturn(native.paramSpecProp(this[HANDLE], 'defaultValue'));
|
|
589
|
+
},
|
|
590
|
+
};
|
|
591
|
+
|
|
546
592
|
function wrapParamSpec(handle) {
|
|
547
|
-
const
|
|
548
|
-
|
|
549
|
-
[HANDLE]: handle,
|
|
550
|
-
get name() {
|
|
551
|
-
return prop('name');
|
|
552
|
-
},
|
|
553
|
-
get nick() {
|
|
554
|
-
return prop('nick');
|
|
555
|
-
},
|
|
556
|
-
get blurb() {
|
|
557
|
-
return prop('blurb');
|
|
558
|
-
},
|
|
559
|
-
get flags() {
|
|
560
|
-
return prop('flags');
|
|
561
|
-
},
|
|
562
|
-
get value_type() {
|
|
563
|
-
return prop('valueType');
|
|
564
|
-
},
|
|
565
|
-
get owner_type() {
|
|
566
|
-
return prop('ownerType');
|
|
567
|
-
},
|
|
568
|
-
get default_value() {
|
|
569
|
-
return wrapReturn(prop('defaultValue'));
|
|
570
|
-
},
|
|
571
|
-
get_name: () => prop('name'),
|
|
572
|
-
get_nick: () => prop('nick'),
|
|
573
|
-
get_blurb: () => prop('blurb'),
|
|
574
|
-
get_default_value: () => wrapReturn(prop('defaultValue')),
|
|
575
|
-
};
|
|
593
|
+
const pspec = Object.create(paramSpecProto);
|
|
594
|
+
pspec[HANDLE] = handle;
|
|
576
595
|
return pspec;
|
|
577
596
|
}
|
|
578
597
|
|
|
@@ -911,13 +930,50 @@ function decorateGioNamespace(baseNs) {
|
|
|
911
930
|
});
|
|
912
931
|
}
|
|
913
932
|
|
|
933
|
+
// The internals of the PROXY links on an introspected prototype chain: the Proxy
|
|
934
|
+
// itself -> its target plus the materializer its getOwnPropertyDescriptor trap runs
|
|
935
|
+
// (`null` for a Proxy that traps no descriptor read). Registered by
|
|
936
|
+
// makeClassPrototype and vfuncChainProto; read only by findProtoDescriptor.
|
|
937
|
+
//
|
|
938
|
+
// Why: a descriptor read THROUGH a Proxy costs ~110 ns of V8 invariant machinery on
|
|
939
|
+
// top of the handler, and an introspected chain is mostly Proxies. Measured on the
|
|
940
|
+
// bench's depth-3 subject, a full MISS walk took 328 ns — 154 ns on the
|
|
941
|
+
// makeClassPrototype link, 65 ns on vfuncChainProto, 12 ns on the one plain link.
|
|
942
|
+
// With the target in hand the same answer costs 27 ns, because the trap body is
|
|
943
|
+
// exactly `materialize(prop); return Reflect.getOwnPropertyDescriptor(target, prop)`
|
|
944
|
+
// and running it directly skips only the machinery, never a step of the computation.
|
|
945
|
+
//
|
|
946
|
+
// This is NOT a cache of the ANSWER: nothing is remembered between calls. The target
|
|
947
|
+
// is the object every mutation of the prototype lands on — none of the handlers trap
|
|
948
|
+
// `set`, `defineProperty`, `deleteProperty` or `setPrototypeOf`, so
|
|
949
|
+
// `Cls.prototype.m = f`, `Object.defineProperty(…)`, `delete` and a spliced chain
|
|
950
|
+
// write through to it — and the descriptor still comes from a fresh read of it. That
|
|
951
|
+
// has to hold, because GJS pins nothing: on gjs 1.88.1 every one of those mutations
|
|
952
|
+
// is observable on an already-wrapped instance (measured), so an answer cache would
|
|
953
|
+
// need an invalidation signal JS does not offer. test/proto-descriptor-liveness.test.mjs
|
|
954
|
+
// is the proof obligation.
|
|
955
|
+
const protoInternals = new WeakMap();
|
|
956
|
+
|
|
914
957
|
// Walk a JS prototype chain (excluding Object.prototype) for an own property
|
|
915
958
|
// descriptor of `prop` — used to surface a registerClass subclass's own methods
|
|
916
|
-
// /getters/setters on the GObject wrapper.
|
|
959
|
+
// /getters/setters on the GObject wrapper. One full walk runs per property GET, per
|
|
960
|
+
// SET and per `emit`, so the Proxy bypass above is on the hottest path there is.
|
|
917
961
|
function findProtoDescriptor(proto, prop) {
|
|
918
|
-
for (let p = proto; p !== null && p !== Object.prototype;
|
|
919
|
-
const
|
|
962
|
+
for (let p = proto; p !== null && p !== Object.prototype;) {
|
|
963
|
+
const internals = protoInternals.get(p);
|
|
964
|
+
if (internals === undefined) {
|
|
965
|
+
const d = Object.getOwnPropertyDescriptor(p, prop);
|
|
966
|
+
if (d !== undefined) return d;
|
|
967
|
+
p = Object.getPrototypeOf(p);
|
|
968
|
+
continue;
|
|
969
|
+
}
|
|
970
|
+
const { target, materialize } = internals;
|
|
971
|
+
if (materialize !== null) materialize(prop);
|
|
972
|
+
const d = Object.getOwnPropertyDescriptor(target, prop);
|
|
920
973
|
if (d !== undefined) return d;
|
|
974
|
+
// The target's prototype, not the Proxy's: neither handler traps
|
|
975
|
+
// getPrototypeOf, so these are the same link — one fewer trap to pay.
|
|
976
|
+
p = Object.getPrototypeOf(target);
|
|
921
977
|
}
|
|
922
978
|
return undefined;
|
|
923
979
|
}
|
|
@@ -1063,7 +1119,27 @@ function assignTemplateChildren(instance, handle, reg) {
|
|
|
1063
1119
|
// resolves the same three spellings at the point of use instead — same reachability,
|
|
1064
1120
|
// no accessor synthesis. Learn6502's SourceView is exactly why this matters: its
|
|
1065
1121
|
// property is `line-numbers` and its accessor is `lineNumbers`.
|
|
1122
|
+
// Resolved per prototype, INCLUDING the "this class declares none" answer (null).
|
|
1123
|
+
// The native store calls runJsPropertySetter on every write of a custom property, so
|
|
1124
|
+
// without this a class with no accessor at all — the common case — paid three
|
|
1125
|
+
// prototype-chain walks and two regex rewrites per assignment to learn nothing. GJS
|
|
1126
|
+
// settles the same question once, at registerClass time (`_checkAccessors` mirrors
|
|
1127
|
+
// the declared spelling onto the other two); a prototype gaining an accessor later is
|
|
1128
|
+
// outside what either runtime honours.
|
|
1129
|
+
const propertySettersByProto = new WeakMap();
|
|
1130
|
+
|
|
1066
1131
|
function findPropertySetter(userProto, propertyName) {
|
|
1132
|
+
let perProto = propertySettersByProto.get(userProto);
|
|
1133
|
+
if (perProto === undefined) propertySettersByProto.set(userProto, (perProto = new Map()));
|
|
1134
|
+
let setter = perProto.get(propertyName);
|
|
1135
|
+
if (setter === undefined) {
|
|
1136
|
+
setter = resolvePropertySetter(userProto, propertyName) ?? null;
|
|
1137
|
+
perProto.set(propertyName, setter);
|
|
1138
|
+
}
|
|
1139
|
+
return setter ?? undefined;
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
function resolvePropertySetter(userProto, propertyName) {
|
|
1067
1143
|
const underscored = propertyName.replace(/-/g, '_');
|
|
1068
1144
|
const camel = propertyName.replace(/-([a-z])/g, (m) => m[1].toUpperCase());
|
|
1069
1145
|
for (const spelling of [propertyName, underscored, camel]) {
|
|
@@ -1095,9 +1171,9 @@ function runJsPropertySetter(handle, propertyName) {
|
|
|
1095
1171
|
// function` until the next release republishes the binary. Every native call added
|
|
1096
1172
|
// here degrades instead; without the callback, a post-construction set simply does
|
|
1097
1173
|
// not reach the JS setter, which is what 0.50.0 did.
|
|
1098
|
-
|
|
1099
|
-
native.setPropertySetCallback(runJsPropertySetter);
|
|
1100
|
-
}
|
|
1174
|
+
onNativeLoaded(() => {
|
|
1175
|
+
if (typeof native.setPropertySetCallback === 'function') native.setPropertySetCallback(runJsPropertySetter);
|
|
1176
|
+
});
|
|
1101
1177
|
|
|
1102
1178
|
// Which properties the construct-time replay must run.
|
|
1103
1179
|
//
|
|
@@ -1306,6 +1382,7 @@ export function startMainContextPump() {
|
|
|
1306
1382
|
// GLib's default main context — a JS-timer pump would only iterate the
|
|
1307
1383
|
// context recursively from within its own dispatch.
|
|
1308
1384
|
if (hostPumpsItself()) return () => {};
|
|
1385
|
+
ensureNative();
|
|
1309
1386
|
pumpRefCount++;
|
|
1310
1387
|
if (pumpTimer === null) {
|
|
1311
1388
|
if (pendingView === null) pendingView = native.makePumpPendingCount();
|
|
@@ -1548,6 +1625,30 @@ function wrapInstance(handle, userProto) {
|
|
|
1548
1625
|
materialised.add(name);
|
|
1549
1626
|
return had;
|
|
1550
1627
|
};
|
|
1628
|
+
// The `connect`/`connect_after`/`emit`/`disconnect` thunks of this wrapper, each
|
|
1629
|
+
// built on its first use (see the `get` trap). A null prototype so an unrelated
|
|
1630
|
+
// name — `constructor`, `toString` — cannot be mistaken for a built shortcut.
|
|
1631
|
+
const signalShortcuts = Object.create(null);
|
|
1632
|
+
// The GI-method thunks of this wrapper, built on first use, `null` for a name
|
|
1633
|
+
// that resolves to no introspected method. A thunk is a pure function of
|
|
1634
|
+
// (handle, name) and presence is already memoized per GType (instanceHasMethod),
|
|
1635
|
+
// so the only thing that changes by keeping it is IDENTITY — and a STABLE one is
|
|
1636
|
+
// the GJS shape: there the method lives on the prototype, so `obj.m === obj.m`
|
|
1637
|
+
// (the reason a user prototype's own function is handed back unbound, #1175).
|
|
1638
|
+
// Built fresh, every `obj.m(…)` in a loop allocated a closure and re-asked for
|
|
1639
|
+
// the type name across the N-API boundary. Null prototype: `constructor` and
|
|
1640
|
+
// friends must not read as a cached thunk.
|
|
1641
|
+
const giMethodThunks = Object.create(null);
|
|
1642
|
+
const giMethodThunk = (prop) => {
|
|
1643
|
+
let thunk = giMethodThunks[prop];
|
|
1644
|
+
if (thunk === undefined) {
|
|
1645
|
+
thunk = instanceHasMethod(handle, prop)
|
|
1646
|
+
? (...args) => wrapReturn(native.callMethod(handle, prop, unwrapArgs(args)))
|
|
1647
|
+
: null;
|
|
1648
|
+
giMethodThunks[prop] = thunk;
|
|
1649
|
+
}
|
|
1650
|
+
return thunk ?? undefined;
|
|
1651
|
+
};
|
|
1551
1652
|
const proxy = new Proxy(target, {
|
|
1552
1653
|
get(t, prop) {
|
|
1553
1654
|
if (prop === HANDLE) return handle;
|
|
@@ -1561,10 +1662,16 @@ function wrapInstance(handle, userProto) {
|
|
|
1561
1662
|
// introspectable ancestor (GObject.Object), yet on gjs 1.88.1
|
|
1562
1663
|
// `socks5.connect` IS g_proxy_connect (Gio.Proxy, 3 IN args) — measured.
|
|
1563
1664
|
// The same native walk the GI-method fallback below uses sees it.
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
}
|
|
1665
|
+
const method = giMethodThunk(prop);
|
|
1666
|
+
if (method !== undefined) return method;
|
|
1567
1667
|
}
|
|
1668
|
+
// The four signal shortcuts below close over nothing but `handle`, so they
|
|
1669
|
+
// are per-wrapper constants — yet the switch rebuilt one on EVERY access,
|
|
1670
|
+
// i.e. one throwaway closure per `obj.emit(…)` in a loop. Built once here
|
|
1671
|
+
// and reused; on gjs these live on GObject.Object.prototype and have stable
|
|
1672
|
+
// identity too. Lazy, because most wrappers never reach for one.
|
|
1673
|
+
const shortcut = signalShortcuts[prop];
|
|
1674
|
+
if (shortcut !== undefined) return shortcut;
|
|
1568
1675
|
switch (prop) {
|
|
1569
1676
|
case '$typeName':
|
|
1570
1677
|
// The instance's concrete RUNTIME GType name —
|
|
@@ -1581,24 +1688,25 @@ function wrapInstance(handle, userProto) {
|
|
|
1581
1688
|
case 'connect':
|
|
1582
1689
|
// Record the (user fn → id) mapping so signal_handlers_*_by_func can
|
|
1583
1690
|
// resolve the private-closure handler back to its ids (see the registry).
|
|
1584
|
-
return (signal, cb) => {
|
|
1691
|
+
return (signalShortcuts.connect = (signal, cb) => {
|
|
1585
1692
|
const id = native.connectSignal(handle, signal, wrapSignalCallback(cb), false);
|
|
1586
1693
|
recordSignalHandler(handle, cb, id);
|
|
1587
1694
|
return id;
|
|
1588
|
-
};
|
|
1695
|
+
});
|
|
1589
1696
|
case 'connect_after':
|
|
1590
|
-
return (signal, cb) => {
|
|
1697
|
+
return (signalShortcuts.connect_after = (signal, cb) => {
|
|
1591
1698
|
const id = native.connectSignal(handle, signal, wrapSignalCallback(cb), true);
|
|
1592
1699
|
recordSignalHandler(handle, cb, id);
|
|
1593
1700
|
return id;
|
|
1594
|
-
};
|
|
1701
|
+
});
|
|
1595
1702
|
case 'emit':
|
|
1596
|
-
return (signal, ...args) =>
|
|
1703
|
+
return (signalShortcuts.emit = (signal, ...args) =>
|
|
1704
|
+
wrapReturn(native.emitSignal(handle, signal, unwrapArgs(args))));
|
|
1597
1705
|
case 'disconnect':
|
|
1598
|
-
return (id) => {
|
|
1706
|
+
return (signalShortcuts.disconnect = (id) => {
|
|
1599
1707
|
forgetSignalHandlerId(handle, id);
|
|
1600
1708
|
return native.disconnectSignal(handle, id);
|
|
1601
|
-
};
|
|
1709
|
+
});
|
|
1602
1710
|
default:
|
|
1603
1711
|
break;
|
|
1604
1712
|
}
|
|
@@ -1674,10 +1782,7 @@ function wrapInstance(handle, userProto) {
|
|
|
1674
1782
|
// camelCase like Gwebgl's `getString`) and only falls back to the
|
|
1675
1783
|
// snake_case alias. Converting here would destroy a literal camelCase
|
|
1676
1784
|
// GI name.
|
|
1677
|
-
|
|
1678
|
-
return (...args) => wrapReturn(native.callMethod(handle, prop, unwrapArgs(args)));
|
|
1679
|
-
}
|
|
1680
|
-
return undefined;
|
|
1785
|
+
return giMethodThunk(prop);
|
|
1681
1786
|
},
|
|
1682
1787
|
set(t, prop, value) {
|
|
1683
1788
|
if (typeof prop === 'string') {
|
|
@@ -1748,7 +1853,8 @@ function wrapInstance(handle, userProto) {
|
|
|
1748
1853
|
// the introspected parent's `vfunc_x` is a thunk into the actual C vtable entry.
|
|
1749
1854
|
// Non-`vfunc_` lookups fall through to Object.prototype (so the base prototype
|
|
1750
1855
|
// keeps its normal object behaviour).
|
|
1751
|
-
const
|
|
1856
|
+
const vfuncChainTarget = Object.create(Object.prototype);
|
|
1857
|
+
const vfuncChainProto = new Proxy(vfuncChainTarget, {
|
|
1752
1858
|
get(target, prop, receiver) {
|
|
1753
1859
|
if (typeof prop === 'string' && prop.startsWith('vfunc_')) {
|
|
1754
1860
|
const vfuncName = prop.slice('vfunc_'.length);
|
|
@@ -1763,6 +1869,10 @@ const vfuncChainProto = new Proxy(Object.create(Object.prototype), {
|
|
|
1763
1869
|
return Reflect.get(target, prop, receiver);
|
|
1764
1870
|
},
|
|
1765
1871
|
});
|
|
1872
|
+
// No descriptor trap here: a `vfunc_*` access is synthesized by `get` alone and was
|
|
1873
|
+
// never reported as a descriptor, so the walk's answer for this link always came from
|
|
1874
|
+
// the target. `null` materializer = "read the target, that is the whole trap".
|
|
1875
|
+
protoInternals.set(vfuncChainProto, { target: vfuncChainTarget, materialize: null });
|
|
1766
1876
|
|
|
1767
1877
|
// Surface a lazy `$gtype` getter on an introspected class/struct ctor: the real
|
|
1768
1878
|
// GObject GType (a node-gi GType handle), resolved on first read via the engine
|
|
@@ -1930,7 +2040,7 @@ function makeClassPrototype(namespace, typeName) {
|
|
|
1930
2040
|
}
|
|
1931
2041
|
return arity >= 0 && define(prop, makeGiMethod(where, prop, arity));
|
|
1932
2042
|
};
|
|
1933
|
-
|
|
2043
|
+
const proto = new Proxy(target, {
|
|
1934
2044
|
get(t, prop, receiver) {
|
|
1935
2045
|
materialize(prop);
|
|
1936
2046
|
return Reflect.get(t, prop, receiver);
|
|
@@ -1943,6 +2053,10 @@ function makeClassPrototype(namespace, typeName) {
|
|
|
1943
2053
|
return materialize(prop) || Reflect.has(t, prop);
|
|
1944
2054
|
},
|
|
1945
2055
|
});
|
|
2056
|
+
// Let findProtoDescriptor run the descriptor trap's body directly on the target —
|
|
2057
|
+
// same two steps, without V8's per-trap invariant checks (see protoInternals).
|
|
2058
|
+
protoInternals.set(proto, { target, materialize });
|
|
2059
|
+
return proto;
|
|
1946
2060
|
}
|
|
1947
2061
|
|
|
1948
2062
|
function makeClass(namespace, typeName) {
|
|
@@ -2462,6 +2576,8 @@ function paramSpecGTyped(type) {
|
|
|
2462
2576
|
const ParamSpec = Object.freeze({
|
|
2463
2577
|
string: paramSpecPlain('string'),
|
|
2464
2578
|
boolean: paramSpecPlain('boolean'),
|
|
2579
|
+
char: paramSpecRanged('char'),
|
|
2580
|
+
uchar: paramSpecRanged('uchar'),
|
|
2465
2581
|
int: paramSpecRanged('int'),
|
|
2466
2582
|
uint: paramSpecRanged('uint'),
|
|
2467
2583
|
int64: paramSpecRanged('int64'),
|
|
@@ -3126,6 +3242,7 @@ function mirrorWindowingEnvIntoCrt() {
|
|
|
3126
3242
|
* @returns {Record<string, unknown>}
|
|
3127
3243
|
*/
|
|
3128
3244
|
export function requireGi(namespace, version) {
|
|
3245
|
+
ensureNative();
|
|
3129
3246
|
native.requireNamespace(namespace, version);
|
|
3130
3247
|
// The one moment the win32 CRT-environment mirror can run: the addon is loaded, so there
|
|
3131
3248
|
// is a GLib to call, and nothing has built a font map or loaded a fontconfig
|
package/host.nativescript.js
CHANGED
|
@@ -44,6 +44,12 @@
|
|
|
44
44
|
/** @type {'nativescript'} */
|
|
45
45
|
export const RUNTIME = 'nativescript';
|
|
46
46
|
|
|
47
|
+
/**
|
|
48
|
+
* Whether the addon may be loaded on first use instead of at import — see index.js.
|
|
49
|
+
* @type {boolean}
|
|
50
|
+
*/
|
|
51
|
+
export const LAZY_NATIVE = true;
|
|
52
|
+
|
|
47
53
|
// Timers from the global scope: NativeScript has no node:timers, and there is no
|
|
48
54
|
// GLib-backed replacement to avoid here. setImmediate is not a V8 builtin.
|
|
49
55
|
export const setImmediate =
|
package/host.node.js
CHANGED
|
@@ -55,6 +55,12 @@ export const RUNTIME =
|
|
|
55
55
|
? 'deno'
|
|
56
56
|
: 'node';
|
|
57
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Whether the addon may be loaded on first use instead of at import — see index.js.
|
|
60
|
+
* @type {boolean}
|
|
61
|
+
*/
|
|
62
|
+
export const LAZY_NATIVE = false;
|
|
63
|
+
|
|
58
64
|
// GJS host mode: `GjsifyNapi.init()` installs the loader as
|
|
59
65
|
// `globalThis.__gjsifyNapiLoadAddon` and re-installs it if another consumer (the
|
|
60
66
|
// @gjsify/napi L1) already captured + deleted it. Needs the GjsifyNapi typelib
|