@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/gi.js
CHANGED
|
@@ -48,11 +48,21 @@ function camelToSnake(name) {
|
|
|
48
48
|
return name.replace(/([A-Z])/g, '_$1').toLowerCase();
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
// Memoized: the wrapper's get/set/has traps convert on EVERY property access, the
|
|
52
|
+
// answer depends on nothing but the name, and the set of names a program uses is
|
|
53
|
+
// bounded by its source text — three regex passes per access buy nothing.
|
|
54
|
+
const kebabNames = new Map();
|
|
55
|
+
|
|
51
56
|
function toKebab(name) {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
57
|
+
let kebab = kebabNames.get(name);
|
|
58
|
+
if (kebab === undefined) {
|
|
59
|
+
kebab = name
|
|
60
|
+
.replace(/([A-Z])/g, '-$1')
|
|
61
|
+
.replace(/_/g, '-')
|
|
62
|
+
.toLowerCase();
|
|
63
|
+
kebabNames.set(name, kebab);
|
|
64
|
+
}
|
|
65
|
+
return kebab;
|
|
56
66
|
}
|
|
57
67
|
|
|
58
68
|
// Repeated passes of the same callback must marshal to the SAME native-facing function:
|
|
@@ -118,82 +128,249 @@ function unwrapProps(props) {
|
|
|
118
128
|
return out;
|
|
119
129
|
}
|
|
120
130
|
|
|
121
|
-
//
|
|
122
|
-
//
|
|
123
|
-
//
|
|
124
|
-
//
|
|
125
|
-
//
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
//
|
|
129
|
-
// builder registered below) and `requireGi('GLib').Error` is this constructor, so a
|
|
130
|
-
// caught error is `instanceof GLib.Error` exactly as under GJS.
|
|
131
|
+
// ---- GLib.Error (L1) ----
|
|
132
|
+
//
|
|
133
|
+
// ONE object for every GError, as on GJS: a boxed handle over a real `GError*` whose
|
|
134
|
+
// CLASS is the error DOMAIN's class (`Gio.IOErrorEnum`) when the quark names a
|
|
135
|
+
// registered domain, and `GLib.Error` otherwise. There is no JS `Error` subclass
|
|
136
|
+
// behind it — `Error.isError()` is true while `instanceof Error` is FALSE, a
|
|
137
|
+
// combination only `Reflect.construct(Error, [], Ctor)` reaches (GJS gets it from
|
|
138
|
+
// `JSCLASS_IS_DOMJSCLASS` on ErrorBase::klass).
|
|
131
139
|
//
|
|
132
|
-
//
|
|
133
|
-
//
|
|
134
|
-
//
|
|
135
|
-
|
|
136
|
-
//
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
140
|
+
// Reference: refs/gjs/gi/gerror.cpp (ErrorBase/ErrorInstance/ErrorPrototype) and its
|
|
141
|
+
// `gjs_define_error_properties`, plus the GLib.js override's `Error.new_literal`
|
|
142
|
+
// guard and `Error.prototype.matches`. The old divergence — an `Error` subclass with
|
|
143
|
+
// `.domain` the quark NAME and the number on a node-gi-only `.domainQuark`, beside a
|
|
144
|
+
// SECOND plain-boxed shape for a marshalled one — is gone with no compat shim;
|
|
145
|
+
// test/gerror-identity.test.mjs is the contract.
|
|
146
|
+
|
|
147
|
+
// quark → the class makeErrorDomainClass built for it. A cached `null` means "asked
|
|
148
|
+
// the repository, this quark has no domain class".
|
|
149
|
+
const errorClassByQuark = new Map();
|
|
150
|
+
|
|
151
|
+
// GJS defines the error methods enumerable and non-configurable, which is observable:
|
|
152
|
+
// a formatter listing a prototype's methods sees them, and nothing can redefine them.
|
|
153
|
+
const errorMethod = (value) => ({ value, enumerable: true, writable: true, configurable: false });
|
|
154
|
+
|
|
155
|
+
// The class for a GError's domain quark, or null when no typelib registers one (a
|
|
156
|
+
// private quark from `GLib.quark_from_string`), in which case the error is a plain
|
|
157
|
+
// `GLib.Error`.
|
|
158
|
+
function errorClassForQuark(quark) {
|
|
159
|
+
const cached = errorClassByQuark.get(quark);
|
|
160
|
+
if (cached !== undefined) return cached;
|
|
161
|
+
// Nothing has touched the enum yet, so the map cannot know it: ask the repository
|
|
162
|
+
// which { namespace, name } registered the domain and READ that member — makeEnum
|
|
163
|
+
// registers the class it builds on the way out. Without this an error whose domain
|
|
164
|
+
// enum the application never named would fall back to the base class, which is
|
|
165
|
+
// exactly the `catch (e) { if (e instanceof Gio.IOErrorEnum) }` case.
|
|
166
|
+
// Guarded for the cross-version pairing the `native.setPropertySetCallback` guard
|
|
167
|
+
// below describes: an addon older than this JS does not export it. Degrading means
|
|
168
|
+
// falling back to what the map alone knows — a domain whose enum `makeEnum` has
|
|
169
|
+
// already realized is in it, one nothing has touched yet lands on the base class.
|
|
170
|
+
// Nothing is cached in that case, so a later `makeEnum` still registers.
|
|
171
|
+
if (typeof native.errorDomainInfo !== 'function') return null;
|
|
172
|
+
const info = native.errorDomainInfo(quark);
|
|
173
|
+
errorClassByQuark.set(quark, null); // also breaks the cycle while the namespace loads
|
|
174
|
+
if (info === null) return null;
|
|
175
|
+
// Namespace MEMBERS are lazy (createNamespace resolves on first read), so the
|
|
176
|
+
// namespace object alone builds nothing — the member read is what runs makeEnum.
|
|
177
|
+
// oxlint-disable-next-line no-unused-expressions -- the READ is the side effect: it runs makeEnum, which registers the class
|
|
178
|
+
requireGi(info.namespace)[info.name];
|
|
179
|
+
return errorClassByQuark.get(quark);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// The first parseable `file:line:column` in a V8 stack. The FRAMES stay V8's — only
|
|
183
|
+
// their position is lifted out, and the frame it lands on is node-gi's own dispatch
|
|
184
|
+
// site for a marshalled error (see docs/node-gi-gjs-surface.md).
|
|
185
|
+
function topFrame(stack) {
|
|
186
|
+
for (const line of String(stack).split('\n')) {
|
|
187
|
+
const match = /\(?([^()\s]+):(\d+):(\d+)\)?$/.exec(line.trim());
|
|
188
|
+
if (match !== null) {
|
|
189
|
+
return { fileName: match[1], lineNumber: Number(match[2]), columnNumber: Number(match[3]) };
|
|
190
|
+
}
|
|
156
191
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
192
|
+
return { fileName: undefined, lineNumber: undefined, columnNumber: undefined };
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// GJS's `gjs_define_error_properties`: four own ENUMERABLE value properties saying
|
|
196
|
+
// where the error came from. Enumerable is the load-bearing half — V8's own `stack`
|
|
197
|
+
// is not, so a `{...err}` spread or a structured log carried the frames on GJS and
|
|
198
|
+
// nothing here.
|
|
199
|
+
function definePositionProperties(error, hideAbove) {
|
|
200
|
+
Error.captureStackTrace(error, hideAbove);
|
|
201
|
+
const stack = error.stack;
|
|
202
|
+
const frame = topFrame(stack);
|
|
203
|
+
for (const [name, value] of [
|
|
204
|
+
['stack', stack],
|
|
205
|
+
['fileName', frame.fileName],
|
|
206
|
+
['lineNumber', frame.lineNumber],
|
|
207
|
+
['columnNumber', frame.columnNumber],
|
|
208
|
+
]) {
|
|
209
|
+
Object.defineProperty(error, name, { value, enumerable: true, writable: true, configurable: true });
|
|
168
210
|
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Wrap a `GError*` handle as the error object. `withPosition` is GJS's asymmetry: the
|
|
214
|
+
// position properties are defined when the error was THROWN or CONSTRUCTED, and
|
|
215
|
+
// otherwise only for an UNKNOWN domain — because GJS then has no domain metadata to
|
|
216
|
+
// build a class from and reaches the GError through its generic struct wrapper, which
|
|
217
|
+
// defines them (gi/struct.cpp). `hideAbove` is the frame the position should be
|
|
218
|
+
// measured at, so `new GLib.Error(…)` reports the caller and not this file.
|
|
219
|
+
function wrapGError(handle, withPosition = false, hideAbove = wrapGError) {
|
|
220
|
+
const cls = errorClassForQuark(native.getBoxedField(handle, 'domain'));
|
|
221
|
+
const error = Reflect.construct(Error, [], cls ?? GLibError);
|
|
222
|
+
error[HANDLE] = handle;
|
|
223
|
+
if (withPosition || cls === null) definePositionProperties(error, hideAbove);
|
|
224
|
+
// `Reflect.construct(Error, …)` always leaves V8's own `stack` behind, and GJS has
|
|
225
|
+
// none on this path at all.
|
|
226
|
+
else delete error.stack;
|
|
227
|
+
return error;
|
|
228
|
+
}
|
|
169
229
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
return domainMatch && code === this.code;
|
|
230
|
+
// g_error_new_literal, behind the quark guard GJS's GLib.js puts in front of it: the
|
|
231
|
+
// domain must already name something, so `new GLib.Error()` is refused here rather
|
|
232
|
+
// than producing a GError no `matches()` can ever hit.
|
|
233
|
+
function newErrorLiteral(domain, code, message) {
|
|
234
|
+
if (native.callFunction('GLib', 'quark_to_string', [domain]) === null) {
|
|
235
|
+
throw new TypeError(`Error.new_literal: ${domain} is not a valid domain`);
|
|
177
236
|
}
|
|
237
|
+
return native.callStaticMethod('GLib', 'Error', 'new_literal', [domain, code, message]);
|
|
238
|
+
}
|
|
178
239
|
|
|
179
|
-
|
|
180
|
-
|
|
240
|
+
// `GLib.Error`: a plain FUNCTION, not a `class … extends Error` — GJS's
|
|
241
|
+
// GLib.Error.prototype has Object.prototype on its chain, not Error.prototype.
|
|
242
|
+
function GLibError(domain, code, message) {
|
|
243
|
+
// The spelling `@girs/glib-2.0`'s `constructor(properties?)` signature invites,
|
|
244
|
+
// and which GJS runs into its generic boxed-struct constructor with: a GError
|
|
245
|
+
// field is not writable. Replicated verbatim so the failure reads the same.
|
|
246
|
+
if (arguments.length === 1 && typeof domain === 'object' && domain !== null) {
|
|
247
|
+
throw new Error('Writing field GLib.Error.message is not supported');
|
|
181
248
|
}
|
|
249
|
+
return wrapGError(newErrorLiteral(domain, code, message), true, GLibError);
|
|
182
250
|
}
|
|
251
|
+
Object.defineProperty(GLibError, 'name', { value: 'GLib_Error', configurable: true });
|
|
252
|
+
defineLazyGType(GLibError, 'GLib', 'Error');
|
|
183
253
|
|
|
184
|
-
//
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
254
|
+
// The static GJS exposes because `g_error_new_literal` is in GLib-2.0.gir — and the
|
|
255
|
+
// only spelling `@girs/glib-2.0` TYPES, so a cross-runtime source that has to build a
|
|
256
|
+
// GError type-checks against this rather than the three positional constructor args.
|
|
257
|
+
GLibError.new_literal = function new_literal(domain, code, message) {
|
|
258
|
+
return wrapGError(newErrorLiteral(domain, code, message));
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
// `domain`/`code`/`message` read straight off the `GError*` through a PROTOTYPE
|
|
262
|
+
// accessor, as GJS does: getter-only (a consumer "normalising" an error by assigning
|
|
263
|
+
// `.domain` is a TypeError, not a silent mutation) and own to each prototype, so the
|
|
264
|
+
// refusal below can name the right one.
|
|
265
|
+
function defineErrorFields(proto, displayName, protoQuark) {
|
|
266
|
+
for (const field of ['domain', 'code', 'message']) {
|
|
267
|
+
Object.defineProperty(proto, field, {
|
|
268
|
+
enumerable: true,
|
|
269
|
+
configurable: false,
|
|
270
|
+
get() {
|
|
271
|
+
const handle = this[HANDLE];
|
|
272
|
+
if (handle !== undefined) return native.getBoxedField(handle, field);
|
|
273
|
+
// A PROTOTYPE has no GError* behind it, and refusing is what keeps a
|
|
274
|
+
// prototype walk (util.inspect, a formatter collecting accessors) from
|
|
275
|
+
// reading a wild pointer. GJS still answers a DOMAIN prototype's
|
|
276
|
+
// `domain` — that is where `matches(Gio.IOErrorEnum, …)` reads the
|
|
277
|
+
// quark from — and refuses the other two.
|
|
278
|
+
if (field === 'domain' && protoQuark !== undefined) return protoQuark;
|
|
279
|
+
throw new Error(`Can't get a field on ${displayName}.prototype; only on instances`);
|
|
280
|
+
},
|
|
281
|
+
});
|
|
282
|
+
}
|
|
190
283
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
284
|
+
|
|
285
|
+
Object.defineProperties(GLibError.prototype, {
|
|
286
|
+
copy: errorMethod(function copy() {
|
|
287
|
+
return wrapGError(native.callBoxedMethod(this[HANDLE], 'copy', []));
|
|
288
|
+
}),
|
|
289
|
+
// g_error_free, for parity. It carries the same double-free footgun as on GJS: the
|
|
290
|
+
// handle still owns the GError* and frees it again on GC.
|
|
291
|
+
free: errorMethod(function free() {
|
|
292
|
+
return native.callBoxedMethod(this[HANDLE], 'free', []);
|
|
293
|
+
}),
|
|
294
|
+
matches: errorMethod(function matches(domain, code) {
|
|
295
|
+
if (arguments.length < 2) {
|
|
296
|
+
throw new TypeError(
|
|
297
|
+
`method GLib.Error.matches: At least 2 arguments required, but only ${arguments.length} passed`,
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
return native.callBoxedMethod(this[HANDLE], 'matches', [domain, code]);
|
|
301
|
+
}),
|
|
302
|
+
});
|
|
303
|
+
defineErrorFields(GLibError.prototype, 'GLib.Error', undefined);
|
|
304
|
+
// Defined SEPARATELY here and on each domain prototype, which is why on GJS
|
|
305
|
+
// `err.toString !== GLib.Error.prototype.toString` for a known domain: there is no
|
|
306
|
+
// sentinel branch, the class that knows its name owns the method.
|
|
307
|
+
Object.defineProperty(
|
|
308
|
+
GLibError.prototype,
|
|
309
|
+
'toString',
|
|
310
|
+
errorMethod(function toString() {
|
|
311
|
+
return `GLib.Error ${native.callFunction('GLib', 'quark_to_string', [this.domain])}: ${this.message}`;
|
|
312
|
+
}),
|
|
313
|
+
);
|
|
314
|
+
|
|
315
|
+
// An error-domain enum (`Gio.IOErrorEnum`) is BOTH the enum and the CLASS of its
|
|
316
|
+
// errors on GJS — `instanceof` discriminates the domain and `new Gio.IOErrorEnum({
|
|
317
|
+
// message, code })` builds one — so it has to be a function with a prototype chained
|
|
318
|
+
// to GLib.Error.prototype. A frozen object, which is what makeEnum hands out
|
|
319
|
+
// otherwise, can be neither.
|
|
320
|
+
function makeErrorDomainClass(namespace, typeName, members, quark) {
|
|
321
|
+
const displayName = `${namespace}.${typeName}`;
|
|
322
|
+
const cls = function (props) {
|
|
323
|
+
// The wording is the contract — a cross-runtime test asserting on a
|
|
324
|
+
// constructor refusal compares the string — and `message` is checked BEFORE
|
|
325
|
+
// `code`, so a call naming neither names `message`.
|
|
326
|
+
if (arguments.length !== 1 || typeof props !== 'object' || props === null) {
|
|
327
|
+
throw new Error('Invalid parameters passed to GError constructor, expected one object');
|
|
328
|
+
}
|
|
329
|
+
if (typeof props.message !== 'string') {
|
|
330
|
+
throw new Error("No property 'message' in GError constructor (or it was not a string)");
|
|
331
|
+
}
|
|
332
|
+
if (!Number.isInteger(props.code) || props.code < -0x80000000 || props.code > 0x7fffffff) {
|
|
333
|
+
throw new Error("No property 'code' in GError constructor (or it was not a 32-bit integer)");
|
|
334
|
+
}
|
|
335
|
+
return wrapGError(newErrorLiteral(quark, props.code, props.message), true, cls);
|
|
336
|
+
};
|
|
337
|
+
Object.defineProperty(cls, 'name', { value: `${namespace}_${typeName}`, configurable: true });
|
|
338
|
+
Object.assign(cls, members);
|
|
339
|
+
// G_TYPE_ERROR, NOT the enum's own GIOErrorEnum: on GJS an error domain is an
|
|
340
|
+
// Error class before it is an enum (ErrorBase::to_gtype), and the enum GType it
|
|
341
|
+
// shadows is unreachable there, so nothing written for GJS can ask for it.
|
|
342
|
+
defineLazyGType(cls, 'GLib', 'Error');
|
|
343
|
+
// A STATIC on GJS (ErrorBase::value_of reads `this.constructor.prototype`'s
|
|
344
|
+
// domain), and what makes both `Number(Gio.IOErrorEnum)` and
|
|
345
|
+
// `err.matches(Gio.IOErrorEnum, code)` answer the quark with no registry lookup.
|
|
346
|
+
cls.valueOf = () => quark;
|
|
347
|
+
Object.setPrototypeOf(cls.prototype, GLibError.prototype);
|
|
348
|
+
defineErrorFields(cls.prototype, displayName, quark);
|
|
349
|
+
Object.defineProperty(
|
|
350
|
+
cls.prototype,
|
|
351
|
+
'toString',
|
|
352
|
+
errorMethod(function toString() {
|
|
353
|
+
return `${displayName}: ${this.message}`;
|
|
354
|
+
}),
|
|
355
|
+
);
|
|
356
|
+
errorClassByQuark.set(quark, cls);
|
|
357
|
+
return cls;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Called by the engine on a failed GI invoke, with ownership of the GError* already
|
|
361
|
+
// transferred to the boxed handle. Named, so the position properties report the
|
|
362
|
+
// application frame rather than this one.
|
|
363
|
+
native.setErrorBuilder(function buildThrownGError(handle, quark, code, message) {
|
|
364
|
+
// Same cross-version pairing: an addon older than this JS calls the builder with
|
|
365
|
+
// the GError's FIELDS (domainName, quark, code, message) rather than a handle, and
|
|
366
|
+
// there is no second `errorClass` argument to give it. Rebuilding the GError from
|
|
367
|
+
// the fields keeps the ONE shape on that pairing too — it costs only the original
|
|
368
|
+
// GError's address, which nothing can read.
|
|
369
|
+
if (typeof handle === 'string') {
|
|
370
|
+
return wrapGError(newErrorLiteral(quark, code, message), true, buildThrownGError);
|
|
371
|
+
}
|
|
372
|
+
return wrapGError(handle, true, buildThrownGError);
|
|
373
|
+
});
|
|
197
374
|
|
|
198
375
|
// Stamps a makeClass prototype with its { namespace, typeName } so Gio._promisify can
|
|
199
376
|
// record WHICH introspected class a registration belongs to — matched against the
|
|
@@ -213,6 +390,13 @@ const promisifiedWrappers = new WeakSet();
|
|
|
213
390
|
const nativeThunks = new WeakSet();
|
|
214
391
|
|
|
215
392
|
function wrapReturn(value) {
|
|
393
|
+
// EVERY engine value comes through here — each return, each OUT tuple element and
|
|
394
|
+
// each signal argument (wrapSignalCallback) — and the five tag probes below are
|
|
395
|
+
// five N-API crossings. A handle is always an OBJECT (a tagged napi External), so a
|
|
396
|
+
// primitive can answer for itself: a `notify::label` handler's `(emitter, pspec)`
|
|
397
|
+
// then costs two probes for the emitter instead of ten for the pair, and the string
|
|
398
|
+
// argument of an ordinary `emit('changed', 'x')` costs none.
|
|
399
|
+
if (value === null || typeof value !== 'object') return value;
|
|
216
400
|
if (native.isGObjectHandle(value)) return wrapInstance(value);
|
|
217
401
|
// A GLib.Variant is ALSO a boxed handle by tag, so it must be checked first to get
|
|
218
402
|
// the Variant ergonomics rather than a plain method-routing proxy.
|
|
@@ -301,6 +485,12 @@ function wrapFundamental(handle) {
|
|
|
301
485
|
// boxedMemberKind checks methods first — 1 = method, 2 = field, 0 = neither,
|
|
302
486
|
// 3 = undecidable.
|
|
303
487
|
function wrapBoxed(handle) {
|
|
488
|
+
// A GError gets the full error shape — domain class, Error brand, position
|
|
489
|
+
// properties — wherever it surfaces: an OUT param, a struct field, a container
|
|
490
|
+
// element. Routing HERE rather than in the GError-typed OUT arm in C++ is what
|
|
491
|
+
// makes those three agree; the old code only special-cased the thrown path, so a
|
|
492
|
+
// GError read out of a container was a bare method-routing proxy.
|
|
493
|
+
if (native.boxedTypeName(handle) === 'GError') return wrapGError(handle);
|
|
304
494
|
const target = { [HANDLE]: handle };
|
|
305
495
|
const methodDispatch =
|
|
306
496
|
(prop) =>
|
|
@@ -354,36 +544,51 @@ function wrapBoxed(handle) {
|
|
|
354
544
|
// GParamSpec-typed value surface as. Carries [HANDLE] so it round-trips back into the
|
|
355
545
|
// engine and `instanceof GObject.ParamSpec` recognises it. value_type/owner_type are
|
|
356
546
|
// native GType handles, like gjs's GType objects.
|
|
547
|
+
// The accessors live HERE, on one shared prototype, and read the handle off `this`.
|
|
548
|
+
// Built per instance they were eleven fresh closures plus a twelve-slot object on
|
|
549
|
+
// EVERY notify emission — the engine hands the pspec to the handler whether or not
|
|
550
|
+
// the handler looks at it, so that allocation landed on every property write of the
|
|
551
|
+
// real-GObject engine. On gjs these same members live on GObject.ParamSpec.prototype,
|
|
552
|
+
// so prototype-held is also the closer shape.
|
|
553
|
+
const paramSpecProto = {
|
|
554
|
+
get name() {
|
|
555
|
+
return native.paramSpecProp(this[HANDLE], 'name');
|
|
556
|
+
},
|
|
557
|
+
get nick() {
|
|
558
|
+
return native.paramSpecProp(this[HANDLE], 'nick');
|
|
559
|
+
},
|
|
560
|
+
get blurb() {
|
|
561
|
+
return native.paramSpecProp(this[HANDLE], 'blurb');
|
|
562
|
+
},
|
|
563
|
+
get flags() {
|
|
564
|
+
return native.paramSpecProp(this[HANDLE], 'flags');
|
|
565
|
+
},
|
|
566
|
+
get value_type() {
|
|
567
|
+
return native.paramSpecProp(this[HANDLE], 'valueType');
|
|
568
|
+
},
|
|
569
|
+
get owner_type() {
|
|
570
|
+
return native.paramSpecProp(this[HANDLE], 'ownerType');
|
|
571
|
+
},
|
|
572
|
+
get default_value() {
|
|
573
|
+
return wrapReturn(native.paramSpecProp(this[HANDLE], 'defaultValue'));
|
|
574
|
+
},
|
|
575
|
+
get_name() {
|
|
576
|
+
return native.paramSpecProp(this[HANDLE], 'name');
|
|
577
|
+
},
|
|
578
|
+
get_nick() {
|
|
579
|
+
return native.paramSpecProp(this[HANDLE], 'nick');
|
|
580
|
+
},
|
|
581
|
+
get_blurb() {
|
|
582
|
+
return native.paramSpecProp(this[HANDLE], 'blurb');
|
|
583
|
+
},
|
|
584
|
+
get_default_value() {
|
|
585
|
+
return wrapReturn(native.paramSpecProp(this[HANDLE], 'defaultValue'));
|
|
586
|
+
},
|
|
587
|
+
};
|
|
588
|
+
|
|
357
589
|
function wrapParamSpec(handle) {
|
|
358
|
-
const
|
|
359
|
-
|
|
360
|
-
[HANDLE]: handle,
|
|
361
|
-
get name() {
|
|
362
|
-
return prop('name');
|
|
363
|
-
},
|
|
364
|
-
get nick() {
|
|
365
|
-
return prop('nick');
|
|
366
|
-
},
|
|
367
|
-
get blurb() {
|
|
368
|
-
return prop('blurb');
|
|
369
|
-
},
|
|
370
|
-
get flags() {
|
|
371
|
-
return prop('flags');
|
|
372
|
-
},
|
|
373
|
-
get value_type() {
|
|
374
|
-
return prop('valueType');
|
|
375
|
-
},
|
|
376
|
-
get owner_type() {
|
|
377
|
-
return prop('ownerType');
|
|
378
|
-
},
|
|
379
|
-
get default_value() {
|
|
380
|
-
return wrapReturn(prop('defaultValue'));
|
|
381
|
-
},
|
|
382
|
-
get_name: () => prop('name'),
|
|
383
|
-
get_nick: () => prop('nick'),
|
|
384
|
-
get_blurb: () => prop('blurb'),
|
|
385
|
-
get_default_value: () => wrapReturn(prop('defaultValue')),
|
|
386
|
-
};
|
|
590
|
+
const pspec = Object.create(paramSpecProto);
|
|
591
|
+
pspec[HANDLE] = handle;
|
|
387
592
|
return pspec;
|
|
388
593
|
}
|
|
389
594
|
|
|
@@ -612,6 +817,13 @@ function makeLogStructured(baseNs) {
|
|
|
612
817
|
}
|
|
613
818
|
|
|
614
819
|
function decorateGLibNamespace(baseNs) {
|
|
820
|
+
// GJS's GLib override installs this on the GLOBAL Error.prototype, which is what
|
|
821
|
+
// makes `catch (e) { if (e.matches(domain, code)) … }` safe to write without a
|
|
822
|
+
// type test in front of it. Plain assignment, so the descriptor is GJS's
|
|
823
|
+
// (writable, enumerable, configurable) rather than a hardened one.
|
|
824
|
+
Error.prototype.matches = function matches() {
|
|
825
|
+
return false;
|
|
826
|
+
};
|
|
615
827
|
// Per-namespace cache of the overlay functions that need the introspected baseNs
|
|
616
828
|
// (log_structured → log_variant; the *_once helpers → idle_add/timeout_add).
|
|
617
829
|
const cache = new Map();
|
|
@@ -658,9 +870,9 @@ function decorateGLibNamespace(baseNs) {
|
|
|
658
870
|
return new Proxy(baseNs, {
|
|
659
871
|
get(t, prop) {
|
|
660
872
|
if (prop === 'Variant') return variantClass;
|
|
661
|
-
// GLib.Error is the L1
|
|
662
|
-
//
|
|
663
|
-
//
|
|
873
|
+
// GLib.Error is the L1 error base class — every GError reaching JS is an
|
|
874
|
+
// instance of it or of its domain's subclass — shadowing the introspected
|
|
875
|
+
// boxed type, which would hand out a bare method-routing proxy.
|
|
664
876
|
if (prop === 'Error') return GLibError;
|
|
665
877
|
if (typeof prop === 'string' && GLIB_OVERLAY_NAMES.has(prop)) return overlay(prop);
|
|
666
878
|
return t[prop];
|
|
@@ -867,7 +1079,27 @@ function assignTemplateChildren(instance, handle, reg) {
|
|
|
867
1079
|
// resolves the same three spellings at the point of use instead — same reachability,
|
|
868
1080
|
// no accessor synthesis. Learn6502's SourceView is exactly why this matters: its
|
|
869
1081
|
// property is `line-numbers` and its accessor is `lineNumbers`.
|
|
1082
|
+
// Resolved per prototype, INCLUDING the "this class declares none" answer (null).
|
|
1083
|
+
// The native store calls runJsPropertySetter on every write of a custom property, so
|
|
1084
|
+
// without this a class with no accessor at all — the common case — paid three
|
|
1085
|
+
// prototype-chain walks and two regex rewrites per assignment to learn nothing. GJS
|
|
1086
|
+
// settles the same question once, at registerClass time (`_checkAccessors` mirrors
|
|
1087
|
+
// the declared spelling onto the other two); a prototype gaining an accessor later is
|
|
1088
|
+
// outside what either runtime honours.
|
|
1089
|
+
const propertySettersByProto = new WeakMap();
|
|
1090
|
+
|
|
870
1091
|
function findPropertySetter(userProto, propertyName) {
|
|
1092
|
+
let perProto = propertySettersByProto.get(userProto);
|
|
1093
|
+
if (perProto === undefined) propertySettersByProto.set(userProto, (perProto = new Map()));
|
|
1094
|
+
let setter = perProto.get(propertyName);
|
|
1095
|
+
if (setter === undefined) {
|
|
1096
|
+
setter = resolvePropertySetter(userProto, propertyName) ?? null;
|
|
1097
|
+
perProto.set(propertyName, setter);
|
|
1098
|
+
}
|
|
1099
|
+
return setter ?? undefined;
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
function resolvePropertySetter(userProto, propertyName) {
|
|
871
1103
|
const underscored = propertyName.replace(/-/g, '_');
|
|
872
1104
|
const camel = propertyName.replace(/-([a-z])/g, (m) => m[1].toUpperCase());
|
|
873
1105
|
for (const spelling of [propertyName, underscored, camel]) {
|
|
@@ -1352,6 +1584,30 @@ function wrapInstance(handle, userProto) {
|
|
|
1352
1584
|
materialised.add(name);
|
|
1353
1585
|
return had;
|
|
1354
1586
|
};
|
|
1587
|
+
// The `connect`/`connect_after`/`emit`/`disconnect` thunks of this wrapper, each
|
|
1588
|
+
// built on its first use (see the `get` trap). A null prototype so an unrelated
|
|
1589
|
+
// name — `constructor`, `toString` — cannot be mistaken for a built shortcut.
|
|
1590
|
+
const signalShortcuts = Object.create(null);
|
|
1591
|
+
// The GI-method thunks of this wrapper, built on first use, `null` for a name
|
|
1592
|
+
// that resolves to no introspected method. A thunk is a pure function of
|
|
1593
|
+
// (handle, name) and presence is already memoized per GType (instanceHasMethod),
|
|
1594
|
+
// so the only thing that changes by keeping it is IDENTITY — and a STABLE one is
|
|
1595
|
+
// the GJS shape: there the method lives on the prototype, so `obj.m === obj.m`
|
|
1596
|
+
// (the reason a user prototype's own function is handed back unbound, #1175).
|
|
1597
|
+
// Built fresh, every `obj.m(…)` in a loop allocated a closure and re-asked for
|
|
1598
|
+
// the type name across the N-API boundary. Null prototype: `constructor` and
|
|
1599
|
+
// friends must not read as a cached thunk.
|
|
1600
|
+
const giMethodThunks = Object.create(null);
|
|
1601
|
+
const giMethodThunk = (prop) => {
|
|
1602
|
+
let thunk = giMethodThunks[prop];
|
|
1603
|
+
if (thunk === undefined) {
|
|
1604
|
+
thunk = instanceHasMethod(handle, prop)
|
|
1605
|
+
? (...args) => wrapReturn(native.callMethod(handle, prop, unwrapArgs(args)))
|
|
1606
|
+
: null;
|
|
1607
|
+
giMethodThunks[prop] = thunk;
|
|
1608
|
+
}
|
|
1609
|
+
return thunk ?? undefined;
|
|
1610
|
+
};
|
|
1355
1611
|
const proxy = new Proxy(target, {
|
|
1356
1612
|
get(t, prop) {
|
|
1357
1613
|
if (prop === HANDLE) return handle;
|
|
@@ -1365,10 +1621,16 @@ function wrapInstance(handle, userProto) {
|
|
|
1365
1621
|
// introspectable ancestor (GObject.Object), yet on gjs 1.88.1
|
|
1366
1622
|
// `socks5.connect` IS g_proxy_connect (Gio.Proxy, 3 IN args) — measured.
|
|
1367
1623
|
// The same native walk the GI-method fallback below uses sees it.
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
}
|
|
1624
|
+
const method = giMethodThunk(prop);
|
|
1625
|
+
if (method !== undefined) return method;
|
|
1371
1626
|
}
|
|
1627
|
+
// The four signal shortcuts below close over nothing but `handle`, so they
|
|
1628
|
+
// are per-wrapper constants — yet the switch rebuilt one on EVERY access,
|
|
1629
|
+
// i.e. one throwaway closure per `obj.emit(…)` in a loop. Built once here
|
|
1630
|
+
// and reused; on gjs these live on GObject.Object.prototype and have stable
|
|
1631
|
+
// identity too. Lazy, because most wrappers never reach for one.
|
|
1632
|
+
const shortcut = signalShortcuts[prop];
|
|
1633
|
+
if (shortcut !== undefined) return shortcut;
|
|
1372
1634
|
switch (prop) {
|
|
1373
1635
|
case '$typeName':
|
|
1374
1636
|
// The instance's concrete RUNTIME GType name —
|
|
@@ -1385,24 +1647,25 @@ function wrapInstance(handle, userProto) {
|
|
|
1385
1647
|
case 'connect':
|
|
1386
1648
|
// Record the (user fn → id) mapping so signal_handlers_*_by_func can
|
|
1387
1649
|
// resolve the private-closure handler back to its ids (see the registry).
|
|
1388
|
-
return (signal, cb) => {
|
|
1650
|
+
return (signalShortcuts.connect = (signal, cb) => {
|
|
1389
1651
|
const id = native.connectSignal(handle, signal, wrapSignalCallback(cb), false);
|
|
1390
1652
|
recordSignalHandler(handle, cb, id);
|
|
1391
1653
|
return id;
|
|
1392
|
-
};
|
|
1654
|
+
});
|
|
1393
1655
|
case 'connect_after':
|
|
1394
|
-
return (signal, cb) => {
|
|
1656
|
+
return (signalShortcuts.connect_after = (signal, cb) => {
|
|
1395
1657
|
const id = native.connectSignal(handle, signal, wrapSignalCallback(cb), true);
|
|
1396
1658
|
recordSignalHandler(handle, cb, id);
|
|
1397
1659
|
return id;
|
|
1398
|
-
};
|
|
1660
|
+
});
|
|
1399
1661
|
case 'emit':
|
|
1400
|
-
return (signal, ...args) =>
|
|
1662
|
+
return (signalShortcuts.emit = (signal, ...args) =>
|
|
1663
|
+
wrapReturn(native.emitSignal(handle, signal, unwrapArgs(args))));
|
|
1401
1664
|
case 'disconnect':
|
|
1402
|
-
return (id) => {
|
|
1665
|
+
return (signalShortcuts.disconnect = (id) => {
|
|
1403
1666
|
forgetSignalHandlerId(handle, id);
|
|
1404
1667
|
return native.disconnectSignal(handle, id);
|
|
1405
|
-
};
|
|
1668
|
+
});
|
|
1406
1669
|
default:
|
|
1407
1670
|
break;
|
|
1408
1671
|
}
|
|
@@ -1478,10 +1741,7 @@ function wrapInstance(handle, userProto) {
|
|
|
1478
1741
|
// camelCase like Gwebgl's `getString`) and only falls back to the
|
|
1479
1742
|
// snake_case alias. Converting here would destroy a literal camelCase
|
|
1480
1743
|
// GI name.
|
|
1481
|
-
|
|
1482
|
-
return (...args) => wrapReturn(native.callMethod(handle, prop, unwrapArgs(args)));
|
|
1483
|
-
}
|
|
1484
|
-
return undefined;
|
|
1744
|
+
return giMethodThunk(prop);
|
|
1485
1745
|
},
|
|
1486
1746
|
set(t, prop, value) {
|
|
1487
1747
|
if (typeof prop === 'string') {
|
|
@@ -1954,13 +2214,11 @@ function makeEnum(namespace, typeName) {
|
|
|
1954
2214
|
for (const key of Object.keys(raw)) {
|
|
1955
2215
|
out[key.toUpperCase().replace(/-/g, '_')] = raw[key];
|
|
1956
2216
|
}
|
|
1957
|
-
//
|
|
1958
|
-
//
|
|
1959
|
-
//
|
|
2217
|
+
// An enum registered as a GError domain (Gio.IOErrorEnum, …) is the CLASS of its
|
|
2218
|
+
// errors as well, which a frozen object cannot be — it keeps its members on the
|
|
2219
|
+
// function form instead.
|
|
1960
2220
|
const domain = native.getErrorDomain(namespace, typeName);
|
|
1961
|
-
if (domain !== null)
|
|
1962
|
-
Object.defineProperty(out, ERROR_DOMAIN, { value: domain, enumerable: false });
|
|
1963
|
-
}
|
|
2221
|
+
if (domain !== null) return makeErrorDomainClass(namespace, typeName, out, domain.quark);
|
|
1964
2222
|
// `Ns.Enum.$gtype` — gjs answers a registered G_TYPE_ENUM/G_TYPE_FLAGS here, so
|
|
1965
2223
|
// `GObject.type_name(Gio.BusType.$gtype)` is `GBusType` there and was `null` over
|
|
1966
2224
|
// the bridge: an enum object carried no `$gtype` at all. EAGER rather than
|