@gjsify/node-gi 0.63.0 → 0.64.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.js +269 -75
- package/index.d.ts +13 -11
- package/index.js +1 -0
- 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/calls.cc +25 -37
- package/src/class.cc +2 -2
- package/src/common.h +15 -27
- package/src/marshal.cc +42 -89
- package/src/repo.cc +43 -15
package/gi.js
CHANGED
|
@@ -118,82 +118,265 @@ function unwrapProps(props) {
|
|
|
118
118
|
return out;
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
-
//
|
|
122
|
-
// can resolve an error-enum (Gio.IOErrorEnum) to the domain it represents. Attached by
|
|
123
|
-
// makeEnum when the enum is registered as a GError domain.
|
|
124
|
-
// A registry symbol, so a host-side stand-in for an enum the typelib lacks (Gtk.DialogError on
|
|
125
|
-
// Android) can declare itself an error domain without importing this module.
|
|
126
|
-
const ERROR_DOMAIN = Symbol.for('nodeGiErrorDomain');
|
|
127
|
-
|
|
128
|
-
// GLib.Error, L1: the engine throws one of these on a failed sync GI invoke (via the
|
|
129
|
-
// builder registered below) and `requireGi('GLib').Error` is this constructor, so a
|
|
130
|
-
// caught error is `instanceof GLib.Error` exactly as under GJS.
|
|
121
|
+
// ---- GLib.Error (L1) ----
|
|
131
122
|
//
|
|
132
|
-
//
|
|
133
|
-
//
|
|
134
|
-
// domain
|
|
135
|
-
|
|
136
|
-
//
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
123
|
+
// ONE object for every GError, as on GJS: a boxed handle over a real `GError*` whose
|
|
124
|
+
// CLASS is the error DOMAIN's class (`Gio.IOErrorEnum`) when the quark names a
|
|
125
|
+
// registered domain, and `GLib.Error` otherwise. There is no JS `Error` subclass
|
|
126
|
+
// behind it — `Error.isError()` is true while `instanceof Error` is FALSE, a
|
|
127
|
+
// combination only `Reflect.construct(Error, [], Ctor)` reaches (GJS gets it from
|
|
128
|
+
// `JSCLASS_IS_DOMJSCLASS` on ErrorBase::klass).
|
|
129
|
+
//
|
|
130
|
+
// Reference: refs/gjs/gi/gerror.cpp (ErrorBase/ErrorInstance/ErrorPrototype) and its
|
|
131
|
+
// `gjs_define_error_properties`, plus the GLib.js override's `Error.new_literal`
|
|
132
|
+
// guard and `Error.prototype.matches`. The old divergence — an `Error` subclass with
|
|
133
|
+
// `.domain` the quark NAME and the number on a node-gi-only `.domainQuark`, beside a
|
|
134
|
+
// SECOND plain-boxed shape for a marshalled one — is gone with no compat shim;
|
|
135
|
+
// test/gerror-identity.test.mjs is the contract.
|
|
136
|
+
|
|
137
|
+
// quark → the class makeErrorDomainClass built for it. A cached `null` means "asked
|
|
138
|
+
// the repository, this quark has no domain class".
|
|
139
|
+
const errorClassByQuark = new Map();
|
|
140
|
+
|
|
141
|
+
// GJS defines the error methods enumerable and non-configurable, which is observable:
|
|
142
|
+
// a formatter listing a prototype's methods sees them, and nothing can redefine them.
|
|
143
|
+
const errorMethod = (value) => ({ value, enumerable: true, writable: true, configurable: false });
|
|
144
|
+
|
|
145
|
+
// The class for a GError's domain quark, or null when no typelib registers one (a
|
|
146
|
+
// private quark from `GLib.quark_from_string`), in which case the error is a plain
|
|
147
|
+
// `GLib.Error`.
|
|
148
|
+
function errorClassForQuark(quark) {
|
|
149
|
+
const cached = errorClassByQuark.get(quark);
|
|
150
|
+
if (cached !== undefined) return cached;
|
|
151
|
+
// Nothing has touched the enum yet, so the map cannot know it: ask the repository
|
|
152
|
+
// which { namespace, name } registered the domain and READ that member — makeEnum
|
|
153
|
+
// registers the class it builds on the way out. Without this an error whose domain
|
|
154
|
+
// enum the application never named would fall back to the base class, which is
|
|
155
|
+
// exactly the `catch (e) { if (e instanceof Gio.IOErrorEnum) }` case.
|
|
156
|
+
// Guarded for the cross-version pairing the `native.setPropertySetCallback` guard
|
|
157
|
+
// below describes: an addon older than this JS does not export it. Degrading means
|
|
158
|
+
// falling back to what the map alone knows — a domain whose enum `makeEnum` has
|
|
159
|
+
// already realized is in it, one nothing has touched yet lands on the base class.
|
|
160
|
+
// Nothing is cached in that case, so a later `makeEnum` still registers.
|
|
161
|
+
if (typeof native.errorDomainInfo !== 'function') return null;
|
|
162
|
+
const info = native.errorDomainInfo(quark);
|
|
163
|
+
errorClassByQuark.set(quark, null); // also breaks the cycle while the namespace loads
|
|
164
|
+
if (info === null) return null;
|
|
165
|
+
// Namespace MEMBERS are lazy (createNamespace resolves on first read), so the
|
|
166
|
+
// namespace object alone builds nothing — the member read is what runs makeEnum.
|
|
167
|
+
// oxlint-disable-next-line no-unused-expressions -- the READ is the side effect: it runs makeEnum, which registers the class
|
|
168
|
+
requireGi(info.namespace)[info.name];
|
|
169
|
+
return errorClassByQuark.get(quark);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// The first parseable `file:line:column` in a V8 stack. The FRAMES stay V8's — only
|
|
173
|
+
// their position is lifted out, and the frame it lands on is node-gi's own dispatch
|
|
174
|
+
// site for a marshalled error (see docs/node-gi-gjs-surface.md).
|
|
175
|
+
function topFrame(stack) {
|
|
176
|
+
for (const line of String(stack).split('\n')) {
|
|
177
|
+
const match = /\(?([^()\s]+):(\d+):(\d+)\)?$/.exec(line.trim());
|
|
178
|
+
if (match !== null) {
|
|
179
|
+
return { fileName: match[1], lineNumber: Number(match[2]), columnNumber: Number(match[3]) };
|
|
180
|
+
}
|
|
156
181
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
182
|
+
return { fileName: undefined, lineNumber: undefined, columnNumber: undefined };
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// GJS's `gjs_define_error_properties`: four own ENUMERABLE value properties saying
|
|
186
|
+
// where the error came from. Enumerable is the load-bearing half — V8's own `stack`
|
|
187
|
+
// is not, so a `{...err}` spread or a structured log carried the frames on GJS and
|
|
188
|
+
// nothing here.
|
|
189
|
+
function definePositionProperties(error, hideAbove) {
|
|
190
|
+
Error.captureStackTrace(error, hideAbove);
|
|
191
|
+
const stack = error.stack;
|
|
192
|
+
const frame = topFrame(stack);
|
|
193
|
+
for (const [name, value] of [
|
|
194
|
+
['stack', stack],
|
|
195
|
+
['fileName', frame.fileName],
|
|
196
|
+
['lineNumber', frame.lineNumber],
|
|
197
|
+
['columnNumber', frame.columnNumber],
|
|
198
|
+
]) {
|
|
199
|
+
Object.defineProperty(error, name, { value, enumerable: true, writable: true, configurable: true });
|
|
168
200
|
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Wrap a `GError*` handle as the error object. `withPosition` is GJS's asymmetry: the
|
|
204
|
+
// position properties are defined when the error was THROWN or CONSTRUCTED, and
|
|
205
|
+
// otherwise only for an UNKNOWN domain — because GJS then has no domain metadata to
|
|
206
|
+
// build a class from and reaches the GError through its generic struct wrapper, which
|
|
207
|
+
// defines them (gi/struct.cpp). `hideAbove` is the frame the position should be
|
|
208
|
+
// measured at, so `new GLib.Error(…)` reports the caller and not this file.
|
|
209
|
+
function wrapGError(handle, withPosition = false, hideAbove = wrapGError) {
|
|
210
|
+
const cls = errorClassForQuark(native.getBoxedField(handle, 'domain'));
|
|
211
|
+
const error = Reflect.construct(Error, [], cls ?? GLibError);
|
|
212
|
+
error[HANDLE] = handle;
|
|
213
|
+
if (withPosition || cls === null) definePositionProperties(error, hideAbove);
|
|
214
|
+
// `Reflect.construct(Error, …)` always leaves V8's own `stack` behind, and GJS has
|
|
215
|
+
// none on this path at all.
|
|
216
|
+
else delete error.stack;
|
|
217
|
+
return error;
|
|
218
|
+
}
|
|
169
219
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
220
|
+
// g_error_new_literal, behind the quark guard GJS's GLib.js puts in front of it: the
|
|
221
|
+
// domain must already name something, so `new GLib.Error()` is refused here rather
|
|
222
|
+
// than producing a GError no `matches()` can ever hit.
|
|
223
|
+
function newErrorLiteral(domain, code, message) {
|
|
224
|
+
const quark = toDomainQuark(domain);
|
|
225
|
+
if (native.callFunction('GLib', 'quark_to_string', [quark]) === null) {
|
|
226
|
+
throw new TypeError(`Error.new_literal: ${domain} is not a valid domain`);
|
|
177
227
|
}
|
|
228
|
+
return native.callStaticMethod('GLib', 'Error', 'new_literal', [quark, code, message]);
|
|
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;
|
|
244
|
+
}
|
|
178
245
|
|
|
179
|
-
|
|
180
|
-
|
|
246
|
+
// `GLib.Error`: a plain FUNCTION, not a `class … extends Error` — GJS's
|
|
247
|
+
// GLib.Error.prototype has Object.prototype on its chain, not Error.prototype.
|
|
248
|
+
function GLibError(domain, code, message) {
|
|
249
|
+
// The spelling `@girs/glib-2.0`'s `constructor(properties?)` signature invites,
|
|
250
|
+
// and which GJS runs into its generic boxed-struct constructor with: a GError
|
|
251
|
+
// field is not writable. Replicated verbatim so the failure reads the same.
|
|
252
|
+
if (arguments.length === 1 && typeof domain === 'object' && domain !== null) {
|
|
253
|
+
throw new Error('Writing field GLib.Error.message is not supported');
|
|
181
254
|
}
|
|
255
|
+
return wrapGError(newErrorLiteral(domain, code, message), true, GLibError);
|
|
182
256
|
}
|
|
257
|
+
Object.defineProperty(GLibError, 'name', { value: 'GLib_Error', configurable: true });
|
|
258
|
+
defineLazyGType(GLibError, 'GLib', 'Error');
|
|
183
259
|
|
|
184
|
-
//
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
260
|
+
// The static GJS exposes because `g_error_new_literal` is in GLib-2.0.gir — and the
|
|
261
|
+
// only spelling `@girs/glib-2.0` TYPES, so a cross-runtime source that has to build a
|
|
262
|
+
// GError type-checks against this rather than the three positional constructor args.
|
|
263
|
+
GLibError.new_literal = function new_literal(domain, code, message) {
|
|
264
|
+
return wrapGError(newErrorLiteral(domain, code, message));
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
// `domain`/`code`/`message` read straight off the `GError*` through a PROTOTYPE
|
|
268
|
+
// accessor, as GJS does: getter-only (a consumer "normalising" an error by assigning
|
|
269
|
+
// `.domain` is a TypeError, not a silent mutation) and own to each prototype, so the
|
|
270
|
+
// refusal below can name the right one.
|
|
271
|
+
function defineErrorFields(proto, displayName, protoQuark) {
|
|
272
|
+
for (const field of ['domain', 'code', 'message']) {
|
|
273
|
+
Object.defineProperty(proto, field, {
|
|
274
|
+
enumerable: true,
|
|
275
|
+
configurable: false,
|
|
276
|
+
get() {
|
|
277
|
+
const handle = this[HANDLE];
|
|
278
|
+
if (handle !== undefined) return native.getBoxedField(handle, field);
|
|
279
|
+
// A PROTOTYPE has no GError* behind it, and refusing is what keeps a
|
|
280
|
+
// prototype walk (util.inspect, a formatter collecting accessors) from
|
|
281
|
+
// reading a wild pointer. GJS still answers a DOMAIN prototype's
|
|
282
|
+
// `domain` — that is where `matches(Gio.IOErrorEnum, …)` reads the
|
|
283
|
+
// quark from — and refuses the other two.
|
|
284
|
+
if (field === 'domain' && protoQuark !== undefined) return protoQuark;
|
|
285
|
+
throw new Error(`Can't get a field on ${displayName}.prototype; only on instances`);
|
|
286
|
+
},
|
|
287
|
+
});
|
|
288
|
+
}
|
|
190
289
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
290
|
+
|
|
291
|
+
Object.defineProperties(GLibError.prototype, {
|
|
292
|
+
copy: errorMethod(function copy() {
|
|
293
|
+
return wrapGError(native.callBoxedMethod(this[HANDLE], 'copy', []));
|
|
294
|
+
}),
|
|
295
|
+
// g_error_free, for parity. It carries the same double-free footgun as on GJS: the
|
|
296
|
+
// handle still owns the GError* and frees it again on GC.
|
|
297
|
+
free: errorMethod(function free() {
|
|
298
|
+
return native.callBoxedMethod(this[HANDLE], 'free', []);
|
|
299
|
+
}),
|
|
300
|
+
matches: errorMethod(function matches(domain, code) {
|
|
301
|
+
if (arguments.length < 2) {
|
|
302
|
+
throw new TypeError(
|
|
303
|
+
`method GLib.Error.matches: At least 2 arguments required, but only ${arguments.length} passed`,
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
return native.callBoxedMethod(this[HANDLE], 'matches', [toDomainQuark(domain), code]);
|
|
307
|
+
}),
|
|
308
|
+
});
|
|
309
|
+
defineErrorFields(GLibError.prototype, 'GLib.Error', undefined);
|
|
310
|
+
// Defined SEPARATELY here and on each domain prototype, which is why on GJS
|
|
311
|
+
// `err.toString !== GLib.Error.prototype.toString` for a known domain: there is no
|
|
312
|
+
// sentinel branch, the class that knows its name owns the method.
|
|
313
|
+
Object.defineProperty(
|
|
314
|
+
GLibError.prototype,
|
|
315
|
+
'toString',
|
|
316
|
+
errorMethod(function toString() {
|
|
317
|
+
return `GLib.Error ${native.callFunction('GLib', 'quark_to_string', [this.domain])}: ${this.message}`;
|
|
318
|
+
}),
|
|
319
|
+
);
|
|
320
|
+
|
|
321
|
+
// An error-domain enum (`Gio.IOErrorEnum`) is BOTH the enum and the CLASS of its
|
|
322
|
+
// errors on GJS — `instanceof` discriminates the domain and `new Gio.IOErrorEnum({
|
|
323
|
+
// message, code })` builds one — so it has to be a function with a prototype chained
|
|
324
|
+
// to GLib.Error.prototype. A frozen object, which is what makeEnum hands out
|
|
325
|
+
// otherwise, can be neither.
|
|
326
|
+
function makeErrorDomainClass(namespace, typeName, members, quark) {
|
|
327
|
+
const displayName = `${namespace}.${typeName}`;
|
|
328
|
+
const cls = function (props) {
|
|
329
|
+
// The wording is the contract — a cross-runtime test asserting on a
|
|
330
|
+
// constructor refusal compares the string — and `message` is checked BEFORE
|
|
331
|
+
// `code`, so a call naming neither names `message`.
|
|
332
|
+
if (arguments.length !== 1 || typeof props !== 'object' || props === null) {
|
|
333
|
+
throw new Error('Invalid parameters passed to GError constructor, expected one object');
|
|
334
|
+
}
|
|
335
|
+
if (typeof props.message !== 'string') {
|
|
336
|
+
throw new Error("No property 'message' in GError constructor (or it was not a string)");
|
|
337
|
+
}
|
|
338
|
+
if (!Number.isInteger(props.code) || props.code < -0x80000000 || props.code > 0x7fffffff) {
|
|
339
|
+
throw new Error("No property 'code' in GError constructor (or it was not a 32-bit integer)");
|
|
340
|
+
}
|
|
341
|
+
return wrapGError(newErrorLiteral(quark, props.code, props.message), true, cls);
|
|
342
|
+
};
|
|
343
|
+
Object.defineProperty(cls, 'name', { value: `${namespace}_${typeName}`, configurable: true });
|
|
344
|
+
Object.assign(cls, members);
|
|
345
|
+
// G_TYPE_ERROR, NOT the enum's own GIOErrorEnum: on GJS an error domain is an
|
|
346
|
+
// Error class before it is an enum (ErrorBase::to_gtype), and the enum GType it
|
|
347
|
+
// shadows is unreachable there, so nothing written for GJS can ask for it.
|
|
348
|
+
defineLazyGType(cls, 'GLib', 'Error');
|
|
349
|
+
// A STATIC on GJS (ErrorBase::value_of reads `this.constructor.prototype`'s
|
|
350
|
+
// domain), and what makes both `Number(Gio.IOErrorEnum)` and
|
|
351
|
+
// `err.matches(Gio.IOErrorEnum, code)` answer the quark with no registry lookup.
|
|
352
|
+
cls.valueOf = () => quark;
|
|
353
|
+
Object.setPrototypeOf(cls.prototype, GLibError.prototype);
|
|
354
|
+
defineErrorFields(cls.prototype, displayName, quark);
|
|
355
|
+
Object.defineProperty(
|
|
356
|
+
cls.prototype,
|
|
357
|
+
'toString',
|
|
358
|
+
errorMethod(function toString() {
|
|
359
|
+
return `${displayName}: ${this.message}`;
|
|
360
|
+
}),
|
|
361
|
+
);
|
|
362
|
+
errorClassByQuark.set(quark, cls);
|
|
363
|
+
return cls;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Called by the engine on a failed GI invoke, with ownership of the GError* already
|
|
367
|
+
// transferred to the boxed handle. Named, so the position properties report the
|
|
368
|
+
// application frame rather than this one.
|
|
369
|
+
native.setErrorBuilder(function buildThrownGError(handle, quark, code, message) {
|
|
370
|
+
// Same cross-version pairing: an addon older than this JS calls the builder with
|
|
371
|
+
// the GError's FIELDS (domainName, quark, code, message) rather than a handle, and
|
|
372
|
+
// there is no second `errorClass` argument to give it. Rebuilding the GError from
|
|
373
|
+
// the fields keeps the ONE shape on that pairing too — it costs only the original
|
|
374
|
+
// GError's address, which nothing can read.
|
|
375
|
+
if (typeof handle === 'string') {
|
|
376
|
+
return wrapGError(newErrorLiteral(quark, code, message), true, buildThrownGError);
|
|
377
|
+
}
|
|
378
|
+
return wrapGError(handle, true, buildThrownGError);
|
|
379
|
+
});
|
|
197
380
|
|
|
198
381
|
// Stamps a makeClass prototype with its { namespace, typeName } so Gio._promisify can
|
|
199
382
|
// record WHICH introspected class a registration belongs to — matched against the
|
|
@@ -301,6 +484,12 @@ function wrapFundamental(handle) {
|
|
|
301
484
|
// boxedMemberKind checks methods first — 1 = method, 2 = field, 0 = neither,
|
|
302
485
|
// 3 = undecidable.
|
|
303
486
|
function wrapBoxed(handle) {
|
|
487
|
+
// A GError gets the full error shape — domain class, Error brand, position
|
|
488
|
+
// properties — wherever it surfaces: an OUT param, a struct field, a container
|
|
489
|
+
// element. Routing HERE rather than in the GError-typed OUT arm in C++ is what
|
|
490
|
+
// makes those three agree; the old code only special-cased the thrown path, so a
|
|
491
|
+
// GError read out of a container was a bare method-routing proxy.
|
|
492
|
+
if (native.boxedTypeName(handle) === 'GError') return wrapGError(handle);
|
|
304
493
|
const target = { [HANDLE]: handle };
|
|
305
494
|
const methodDispatch =
|
|
306
495
|
(prop) =>
|
|
@@ -612,6 +801,13 @@ function makeLogStructured(baseNs) {
|
|
|
612
801
|
}
|
|
613
802
|
|
|
614
803
|
function decorateGLibNamespace(baseNs) {
|
|
804
|
+
// GJS's GLib override installs this on the GLOBAL Error.prototype, which is what
|
|
805
|
+
// makes `catch (e) { if (e.matches(domain, code)) … }` safe to write without a
|
|
806
|
+
// type test in front of it. Plain assignment, so the descriptor is GJS's
|
|
807
|
+
// (writable, enumerable, configurable) rather than a hardened one.
|
|
808
|
+
Error.prototype.matches = function matches() {
|
|
809
|
+
return false;
|
|
810
|
+
};
|
|
615
811
|
// Per-namespace cache of the overlay functions that need the introspected baseNs
|
|
616
812
|
// (log_structured → log_variant; the *_once helpers → idle_add/timeout_add).
|
|
617
813
|
const cache = new Map();
|
|
@@ -658,9 +854,9 @@ function decorateGLibNamespace(baseNs) {
|
|
|
658
854
|
return new Proxy(baseNs, {
|
|
659
855
|
get(t, prop) {
|
|
660
856
|
if (prop === 'Variant') return variantClass;
|
|
661
|
-
// GLib.Error is the L1
|
|
662
|
-
//
|
|
663
|
-
//
|
|
857
|
+
// GLib.Error is the L1 error base class — every GError reaching JS is an
|
|
858
|
+
// instance of it or of its domain's subclass — shadowing the introspected
|
|
859
|
+
// boxed type, which would hand out a bare method-routing proxy.
|
|
664
860
|
if (prop === 'Error') return GLibError;
|
|
665
861
|
if (typeof prop === 'string' && GLIB_OVERLAY_NAMES.has(prop)) return overlay(prop);
|
|
666
862
|
return t[prop];
|
|
@@ -1954,13 +2150,11 @@ function makeEnum(namespace, typeName) {
|
|
|
1954
2150
|
for (const key of Object.keys(raw)) {
|
|
1955
2151
|
out[key.toUpperCase().replace(/-/g, '_')] = raw[key];
|
|
1956
2152
|
}
|
|
1957
|
-
//
|
|
1958
|
-
//
|
|
1959
|
-
//
|
|
2153
|
+
// An enum registered as a GError domain (Gio.IOErrorEnum, …) is the CLASS of its
|
|
2154
|
+
// errors as well, which a frozen object cannot be — it keeps its members on the
|
|
2155
|
+
// function form instead.
|
|
1960
2156
|
const domain = native.getErrorDomain(namespace, typeName);
|
|
1961
|
-
if (domain !== null)
|
|
1962
|
-
Object.defineProperty(out, ERROR_DOMAIN, { value: domain, enumerable: false });
|
|
1963
|
-
}
|
|
2157
|
+
if (domain !== null) return makeErrorDomainClass(namespace, typeName, out, domain.quark);
|
|
1964
2158
|
// `Ns.Enum.$gtype` — gjs answers a registered G_TYPE_ENUM/G_TYPE_FLAGS here, so
|
|
1965
2159
|
// `GObject.type_name(Gio.BusType.$gtype)` is `GBusType` there and was `null` over
|
|
1966
2160
|
// the bridge: an enum object carried no `$gtype` at all. EAGER rather than
|
package/index.d.ts
CHANGED
|
@@ -56,17 +56,18 @@ export function getEnumValues(namespace: string, name: string): Record<string, n
|
|
|
56
56
|
export function getErrorDomain(namespace: string, name: string): { name: string; quark: number } | null;
|
|
57
57
|
|
|
58
58
|
/**
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
59
|
+
* Resolve a domain quark back to the enum type registered for it — the reverse of
|
|
60
|
+
* {@link getErrorDomain}, so a thrown error reaches its domain class even when
|
|
61
|
+
* nothing has touched that enum yet. `null` for a quark no typelib claims.
|
|
62
|
+
*/
|
|
63
|
+
export function errorDomainInfo(quark: number): { namespace: string; name: string } | null;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Register the L1 `GLib.Error` factory the engine calls when a GI invoke fails. It
|
|
67
|
+
* receives the failed call's `GError` as a boxed handle, having taken ownership of
|
|
68
|
+
* it, and returns the object to throw — the same shape a marshalled error gets.
|
|
69
|
+
*/
|
|
70
|
+
export function setErrorBuilder(builder: (handle: BoxedHandle) => unknown): void;
|
|
70
71
|
|
|
71
72
|
/** Prepend a directory to the GIRepository typelib search path. */
|
|
72
73
|
export function prependSearchPath(path: string): void;
|
|
@@ -599,6 +600,7 @@ declare const native: {
|
|
|
599
600
|
getConstantValue: typeof getConstantValue;
|
|
600
601
|
getEnumValues: typeof getEnumValues;
|
|
601
602
|
getErrorDomain: typeof getErrorDomain;
|
|
603
|
+
errorDomainInfo: typeof errorDomainInfo;
|
|
602
604
|
setErrorBuilder: typeof setErrorBuilder;
|
|
603
605
|
prependSearchPath: typeof prependSearchPath;
|
|
604
606
|
prependLibraryPath: typeof prependLibraryPath;
|
package/index.js
CHANGED
|
@@ -29,6 +29,7 @@ export const findInfo = native.findInfo;
|
|
|
29
29
|
export const getConstantValue = native.getConstantValue;
|
|
30
30
|
export const getEnumValues = native.getEnumValues;
|
|
31
31
|
export const getErrorDomain = native.getErrorDomain;
|
|
32
|
+
export const errorDomainInfo = native.errorDomainInfo;
|
|
32
33
|
export const setErrorBuilder = native.setErrorBuilder;
|
|
33
34
|
export const prependSearchPath = native.prependSearchPath;
|
|
34
35
|
export const prependLibraryPath = native.prependLibraryPath;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjsify/node-gi",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.64.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/addon.cc
CHANGED
|
@@ -88,6 +88,7 @@ static Napi::Object Init(Napi::Env env, Napi::Object exports) {
|
|
|
88
88
|
exports.Set("getConstantValue", Napi::Function::New(env, GetConstantValue));
|
|
89
89
|
exports.Set("getEnumValues", Napi::Function::New(env, GetEnumValues));
|
|
90
90
|
exports.Set("getErrorDomain", Napi::Function::New(env, GetErrorDomain));
|
|
91
|
+
exports.Set("errorDomainInfo", Napi::Function::New(env, ErrorDomainInfo));
|
|
91
92
|
exports.Set("setErrorBuilder", Napi::Function::New(env, SetErrorBuilder));
|
|
92
93
|
exports.Set("prependSearchPath", Napi::Function::New(env, PrependSearchPath));
|
|
93
94
|
exports.Set("prependLibraryPath", Napi::Function::New(env, PrependLibraryPath));
|
package/src/calls.cc
CHANGED
|
@@ -380,44 +380,40 @@ bool IsSupportedOutType(GITypeInfo* type, std::string* why) {
|
|
|
380
380
|
|
|
381
381
|
// ---- GError -> GLib.Error ----
|
|
382
382
|
//
|
|
383
|
-
// A failed GI invoke yields a GError (domain quark + code + message)
|
|
384
|
-
//
|
|
385
|
-
//
|
|
386
|
-
//
|
|
387
|
-
//
|
|
388
|
-
//
|
|
389
|
-
// what it returns.
|
|
383
|
+
// A failed GI invoke yields a GError (domain quark + code + message), which GJS
|
|
384
|
+
// throws as the error object of that DOMAIN's class. That object is L1's to build
|
|
385
|
+
// (gi.js wrapGError) — only JS can give it the Error brand, the domain class and
|
|
386
|
+
// the position properties — so the engine hands the builder registered in this
|
|
387
|
+
// env's instance data the one thing it needs: the `GError*` itself, as a boxed
|
|
388
|
+
// handle.
|
|
390
389
|
//
|
|
391
|
-
//
|
|
392
|
-
//
|
|
393
|
-
//
|
|
394
|
-
//
|
|
395
|
-
//
|
|
390
|
+
// Passing the HANDLE and not the fields is the whole point. The fields used to be
|
|
391
|
+
// read out, the GError g_error_free'd here, and a JS Error subclass built from
|
|
392
|
+
// them — so a thrown error and a marshalled one were two different objects with
|
|
393
|
+
// two different `.domain` types. Ownership now transfers to the handle, which
|
|
394
|
+
// frees the GError when the JS error is collected, and both directions wrap the
|
|
395
|
+
// same pointer the same way.
|
|
396
|
+
//
|
|
397
|
+
// Falls back to a plain JS Error when no builder is registered for this env;
|
|
398
|
+
// `context` is the "Ns.method" display name, used only in that fallback message (a
|
|
399
|
+
// real GLib.Error carries the bare GError message, matching GJS).
|
|
396
400
|
void ThrowGError(Napi::Env env, GError* error, const std::string& context) {
|
|
397
|
-
// Read everything out of the GError BEFORE g_error_free.
|
|
398
|
-
GQuark domainQuark = error != nullptr ? error->domain : 0;
|
|
399
|
-
const char* domainName = domainQuark != 0 ? g_quark_to_string(domainQuark) : nullptr;
|
|
400
|
-
std::string domain = domainName != nullptr ? domainName : "";
|
|
401
|
-
int code = error != nullptr ? error->code : 0;
|
|
402
401
|
std::string message =
|
|
403
402
|
(error != nullptr && error->message != nullptr) ? error->message : "invocation failed";
|
|
404
|
-
if (error != nullptr) g_error_free(error);
|
|
405
403
|
|
|
406
|
-
// Prefer this env's L1 GLib.Error builder (instanceof GLib.Error + matches()).
|
|
407
404
|
NodeGiEnvData* d = EnvData(env);
|
|
408
|
-
if (d != nullptr && d->errorBuilder != nullptr) {
|
|
405
|
+
if (error != nullptr && d != nullptr && d->errorBuilder != nullptr) {
|
|
409
406
|
napi_value builder = nullptr;
|
|
410
407
|
if (napi_get_reference_value(env, d->errorBuilder, &builder) == napi_ok &&
|
|
411
408
|
builder != nullptr) {
|
|
412
409
|
napi_value undef = nullptr;
|
|
413
410
|
napi_get_undefined(env, &undef);
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
napi_create_string_utf8(env, message.c_str(), NAPI_AUTO_LENGTH, &bargs[3]);
|
|
411
|
+
// GI_TRANSFER_EVERYTHING: the invoke's out-GError is ours, and the handle
|
|
412
|
+
// adopts it rather than copying — so nothing frees it here.
|
|
413
|
+
napi_value handle = MakeGErrorHandle(env, error, GI_TRANSFER_EVERYTHING);
|
|
414
|
+
error = nullptr; // adopted by the handle; no path below may free it again
|
|
419
415
|
napi_value errObj = nullptr;
|
|
420
|
-
napi_status st = napi_call_function(env, undef, builder,
|
|
416
|
+
napi_status st = napi_call_function(env, undef, builder, 1, &handle, &errObj);
|
|
421
417
|
if (st == napi_ok && errObj != nullptr) {
|
|
422
418
|
napi_throw(env, errObj);
|
|
423
419
|
return;
|
|
@@ -427,6 +423,7 @@ void ThrowGError(Napi::Env env, GError* error, const std::string& context) {
|
|
|
427
423
|
if (napi_is_exception_pending(env, &pending) == napi_ok && pending) return;
|
|
428
424
|
}
|
|
429
425
|
}
|
|
426
|
+
if (error != nullptr) g_error_free(error);
|
|
430
427
|
Napi::Error::New(env, "Calling " + context + ": " + message).ThrowAsJavaScriptException();
|
|
431
428
|
}
|
|
432
429
|
|
|
@@ -636,7 +633,6 @@ static Napi::Value InvokeFunctionInfo(Napi::Env env, GIFunctionInfo* func, gpoin
|
|
|
636
633
|
// belong to the callee (dropped only when the callee never ran).
|
|
637
634
|
CreatedBytes createdBytes;
|
|
638
635
|
CreatedValues createdValues;
|
|
639
|
-
CreatedErrors createdErrors;
|
|
640
636
|
bool ok = true;
|
|
641
637
|
size_t jsCursor = 0;
|
|
642
638
|
std::vector<LengthWrite> lengthWrites;
|
|
@@ -815,7 +811,7 @@ static Napi::Value InvokeFunctionInfo(Napi::Env env, GIFunctionInfo* func, gpoin
|
|
|
815
811
|
jsCursor++;
|
|
816
812
|
GITransfer tr = gi_arg_info_get_ownership_transfer(ai);
|
|
817
813
|
if (JsToGIArgument(env, v, ti, &slots[i], &held[i], tr, &ownedInStrings, nullptr,
|
|
818
|
-
nullptr, nullptr,
|
|
814
|
+
nullptr, nullptr,
|
|
819
815
|
gi_base_info_get_name(reinterpret_cast<GIBaseInfo*>(ai)))) {
|
|
820
816
|
in_args[inPos[i]].v_pointer = &slots[i];
|
|
821
817
|
out_args[outPos[i]].v_pointer = &slots[i];
|
|
@@ -922,7 +918,7 @@ static Napi::Value InvokeFunctionInfo(Napi::Env env, GIFunctionInfo* func, gpoin
|
|
|
922
918
|
} else {
|
|
923
919
|
GITransfer tr = gi_arg_info_get_ownership_transfer(ai);
|
|
924
920
|
ok = JsToGIArgument(env, v, ti, &in_args[inPos[i]], &held[i], tr, &ownedInStrings,
|
|
925
|
-
&createdClosures, &createdBytes, &createdValues,
|
|
921
|
+
&createdClosures, &createdBytes, &createdValues,
|
|
926
922
|
gi_base_info_get_name(reinterpret_cast<GIBaseInfo*>(ai)));
|
|
927
923
|
}
|
|
928
924
|
}
|
|
@@ -944,9 +940,6 @@ static Napi::Value InvokeFunctionInfo(Napi::Env env, GIFunctionInfo* func, gpoin
|
|
|
944
940
|
// Likewise every GValue boxed from a plain JS value — never adopted.
|
|
945
941
|
for (GValue* gv : createdValues.transferNone) NodeGiFreeBoxedGValue(gv);
|
|
946
942
|
for (GValue* gv : createdValues.transferFull) NodeGiFreeBoxedGValue(gv);
|
|
947
|
-
// Likewise every GError built from an L1 GLib.Error — never adopted.
|
|
948
|
-
for (GError* e : createdErrors.transferNone) g_error_free(e);
|
|
949
|
-
for (GError* e : createdErrors.transferFull) g_error_free(e);
|
|
950
943
|
// Likewise the transfer-full-instance ref: the callee never consumed it.
|
|
951
944
|
if (instanceRefTaken) g_object_unref(static_cast<GObject*>(instance));
|
|
952
945
|
for (gpointer s : ownedInStrings) g_free(s); // never reached the callee (#658)
|
|
@@ -1001,11 +994,6 @@ static Napi::Value InvokeFunctionInfo(Napi::Env env, GIFunctionInfo* func, gpoin
|
|
|
1001
994
|
// box is ours to free — success AND error paths, mirroring the GBytes rule.
|
|
1002
995
|
// Transfer-full GValues were adopted by the callee — no release.
|
|
1003
996
|
for (GValue* gv : createdValues.transferNone) NodeGiFreeBoxedGValue(gv);
|
|
1004
|
-
// Transfer-none GError IN-args built from an L1 GLib.Error: the callee copied
|
|
1005
|
-
// what it kept (gst_message_new_error copies the GError into the message), so
|
|
1006
|
-
// the build is ours to free — success AND error paths, mirroring the two rules
|
|
1007
|
-
// above. Transfer-full GErrors were adopted by the callee — no release.
|
|
1008
|
-
for (GError* e : createdErrors.transferNone) g_error_free(e);
|
|
1009
997
|
if (!success) {
|
|
1010
998
|
for (const InContainer& c : inContainers) FreeInContainer(c);
|
|
1011
999
|
// A failed invoke did not adopt the transfer-full IN/INOUT strings we g_strdup'd
|
package/src/class.cc
CHANGED
|
@@ -907,7 +907,7 @@ static Napi::Value InvokeVFuncPointer(Napi::Env env, GObject* obj, GICallableInf
|
|
|
907
907
|
jsCursor++;
|
|
908
908
|
GITransfer tr = gi_arg_info_get_ownership_transfer(ai);
|
|
909
909
|
if (JsToGIArgument(env, v, ti, &slots[i], &holds[i], tr, &ownedInStrings, nullptr,
|
|
910
|
-
nullptr, nullptr,
|
|
910
|
+
nullptr, nullptr,
|
|
911
911
|
gi_base_info_get_name(reinterpret_cast<GIBaseInfo*>(ai))))
|
|
912
912
|
giArgs[1 + i].v_pointer = &slots[i];
|
|
913
913
|
else
|
|
@@ -963,7 +963,7 @@ static Napi::Value InvokeVFuncPointer(Napi::Env env, GObject* obj, GICallableInf
|
|
|
963
963
|
jsCursor++;
|
|
964
964
|
GITransfer tr = gi_arg_info_get_ownership_transfer(ai);
|
|
965
965
|
ok = JsToGIArgument(env, v, ti, &giArgs[1 + i], &holds[i], tr, &ownedInStrings, nullptr,
|
|
966
|
-
nullptr, nullptr,
|
|
966
|
+
nullptr, nullptr,
|
|
967
967
|
gi_base_info_get_name(reinterpret_cast<GIBaseInfo*>(ai)));
|
|
968
968
|
}
|
|
969
969
|
|
package/src/common.h
CHANGED
|
@@ -83,11 +83,6 @@ GIRepository* DupDefaultRepository();
|
|
|
83
83
|
// Init, freed by the instance-data finalizer at env teardown.
|
|
84
84
|
struct NodeGiEnvData {
|
|
85
85
|
napi_ref errorBuilder = nullptr;
|
|
86
|
-
// The CLASS the builder above constructs — L1's `GLib.Error`. Registered with
|
|
87
|
-
// the builder (one call, so the two cannot drift apart) and read the other way
|
|
88
|
-
// round: a GError-typed IN arg uses it to recognise an L1 error and rebuild a
|
|
89
|
-
// real GError from its fields. Per-env for the same reason as errorBuilder.
|
|
90
|
-
napi_ref errorClass = nullptr;
|
|
91
86
|
// L1-registered resolver for Gtk.Template `<signal handler="…">` callbacks: given
|
|
92
87
|
// (instanceHandle, handlerName) it returns the instance's bound JS method (or
|
|
93
88
|
// undefined). Stored per-env for the same reason as errorBuilder (a napi_ref is
|
|
@@ -160,6 +155,13 @@ extern const napi_type_tag kGObjectHandleTag;
|
|
|
160
155
|
Napi::Value MakeBoxedHandle(Napi::Env env, gpointer ptr, GType gtype, bool owns,
|
|
161
156
|
GIBaseInfo* info = nullptr, bool rawOwned = false);
|
|
162
157
|
Napi::Value WrapVariant(Napi::Env env, GVariant* var, GITransfer transfer);
|
|
158
|
+
// Wrap a `GError*` as the boxed handle L1's `wrapGError` turns into the error
|
|
159
|
+
// object. ONE helper for both directions a GError reaches JS by — a GError-typed
|
|
160
|
+
// return/OUT and the error a failed invoke THREW — because those used to produce
|
|
161
|
+
// two different JS shapes, and sharing the wrap is what keeps them from drifting
|
|
162
|
+
// apart again. Transfer follows the boxed rule: transfer-everything is adopted,
|
|
163
|
+
// transfer-none is copied, so the handle always owns what it frees.
|
|
164
|
+
Napi::Value MakeGErrorHandle(Napi::Env env, GError* error, GITransfer transfer);
|
|
163
165
|
BoxedHandle* TryGetBoxedHandle(Napi::Value v);
|
|
164
166
|
bool TryGetBoxedPtr(Napi::Value v, gpointer* out);
|
|
165
167
|
Napi::Value MakeGTypeHandle(Napi::Env env, GType gtype);
|
|
@@ -317,13 +319,13 @@ struct CreatedClosures {
|
|
|
317
319
|
|
|
318
320
|
// ---- C objects this layer SYNTHESISES for one IN argument --------------------
|
|
319
321
|
//
|
|
320
|
-
//
|
|
321
|
-
//
|
|
322
|
-
// number/string for a `GObject.Value` arg
|
|
323
|
-
//
|
|
324
|
-
//
|
|
325
|
-
//
|
|
326
|
-
//
|
|
322
|
+
// Two IN arms build a throw-away C object out of a plain JS value, because the JS
|
|
323
|
+
// side has no handle to hand over: a Uint8Array for a `GLib.Bytes` arg, a
|
|
324
|
+
// number/string for a `GObject.Value` arg. Both carry the SAME lifetime rule,
|
|
325
|
+
// which is why they share one type instead of a second near-identical struct:
|
|
326
|
+
// transfer-none → the callee borrowed (or copied) it, so we release it after the
|
|
327
|
+
// invoke; transfer-full → the callee adopted it, so it is released only when the
|
|
328
|
+
// invoke never ran.
|
|
327
329
|
template <typename T>
|
|
328
330
|
struct CreatedIn {
|
|
329
331
|
std::vector<T*> transferNone; // release after the invoke (callee borrowed/copied)
|
|
@@ -353,17 +355,6 @@ using CreatedBytes = CreatedIn<GBytes>;
|
|
|
353
355
|
// boxed-handle path and is passed through untouched.
|
|
354
356
|
using CreatedValues = CreatedIn<GValue>;
|
|
355
357
|
|
|
356
|
-
// An L1 `GLib.Error` (what a `catch` hands you, and what `new GLib.Error(…)`
|
|
357
|
-
// builds) has no `GError*` behind it — it is a JS class, not a boxed handle — so
|
|
358
|
-
// a `GError`-typed IN arg gets a fresh `g_error_new_literal` built from its
|
|
359
|
-
// domain/code/message. Without this, the hand-back direction the OUT direction
|
|
360
|
-
// exists for is only open to errors that came OUT of GI in the first place:
|
|
361
|
-
// `GLib.propagate_error(caught)` and `Gst.Message.new_error(src, caught, dbg)`
|
|
362
|
-
// both refused where gjs accepts, which is the shape an application actually
|
|
363
|
-
// writes. A marshalled GError (a boxed handle) still routes through
|
|
364
|
-
// TransferBoxedIn and is not copied here.
|
|
365
|
-
using CreatedErrors = CreatedIn<GError>;
|
|
366
|
-
|
|
367
358
|
// Release a GValue this layer boxed: unset the held value (dropping the ref an
|
|
368
359
|
// object/boxed/string GValue took) and free the struct. A plain `g_free` would
|
|
369
360
|
// leak whatever the GValue owns.
|
|
@@ -390,9 +381,6 @@ GClosure* NodeGiMakeGenericJsClosure(Napi::Env env, Napi::Value fn);
|
|
|
390
381
|
// `values` (optional): enables the JS-value→GValue IN-arg boxing above; nullptr
|
|
391
382
|
// (the default) keeps the previous behaviour (plain value → TypeError) on paths
|
|
392
383
|
// that cannot release a created GValue.
|
|
393
|
-
// `errors` (optional): enables the L1-GLib.Error→GError IN-arg build above;
|
|
394
|
-
// nullptr (the default) keeps a marshalled boxed handle working and refuses the
|
|
395
|
-
// L1 class, on paths that cannot release a created GError.
|
|
396
384
|
// `argName` (optional): the introspected argument name, used ONLY in error
|
|
397
385
|
// messages so a refusal reads like gjs's ("Expected type string for argument
|
|
398
386
|
// 'name' but got type number"); nullptr degrades to a message without the name.
|
|
@@ -403,7 +391,6 @@ bool JsToGIArgument(Napi::Env env, Napi::Value v, GITypeInfo* type, GIArgument*
|
|
|
403
391
|
CreatedClosures* closures = nullptr,
|
|
404
392
|
CreatedBytes* bytes = nullptr,
|
|
405
393
|
CreatedValues* values = nullptr,
|
|
406
|
-
CreatedErrors* errors = nullptr,
|
|
407
394
|
const char* argName = nullptr);
|
|
408
395
|
|
|
409
396
|
// ---- IN container building -----------------------------------------
|
|
@@ -671,6 +658,7 @@ Napi::Value FindInfo(const Napi::CallbackInfo& info);
|
|
|
671
658
|
Napi::Value GetConstantValue(const Napi::CallbackInfo& info);
|
|
672
659
|
Napi::Value GetEnumValues(const Napi::CallbackInfo& info);
|
|
673
660
|
Napi::Value GetErrorDomain(const Napi::CallbackInfo& info);
|
|
661
|
+
Napi::Value ErrorDomainInfo(const Napi::CallbackInfo& info);
|
|
674
662
|
Napi::Value SetErrorBuilder(const Napi::CallbackInfo& info);
|
|
675
663
|
Napi::Value PrependSearchPath(const Napi::CallbackInfo& info);
|
|
676
664
|
Napi::Value PrependLibraryPath(const Napi::CallbackInfo& info);
|
package/src/marshal.cc
CHANGED
|
@@ -143,6 +143,22 @@ static Napi::Value WrapBoxed(Napi::Env env, gpointer ptr, GIBaseInfo* structInfo
|
|
|
143
143
|
return MakeBoxedHandle(env, handlePtr, handleGType, owns, info);
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
+
// See common.h. Wrapped through the GLib.Error STRUCT info rather than by GType
|
|
147
|
+
// alone, so `domain`/`code`/`message` resolve as fields — which is how L1 reads
|
|
148
|
+
// them off the GError*.
|
|
149
|
+
Napi::Value MakeGErrorHandle(Napi::Env env, GError* error, GITransfer transfer) {
|
|
150
|
+
if (error == nullptr) return env.Null();
|
|
151
|
+
GIRepository* repo = DupDefaultRepository();
|
|
152
|
+
// GLib is loaded by any namespace require; require defensively anyway so a bare
|
|
153
|
+
// engine call still resolves the struct info (idempotent, cheap).
|
|
154
|
+
gi_repository_require(repo, "GLib", "2.0", static_cast<GIRepositoryLoadFlags>(0), nullptr);
|
|
155
|
+
GIBaseInfo* errInfo = gi_repository_find_by_name(repo, "GLib", "Error");
|
|
156
|
+
Napi::Value result = WrapBoxed(env, error, errInfo, transfer);
|
|
157
|
+
if (errInfo != nullptr) gi_base_info_unref(errInfo);
|
|
158
|
+
g_object_unref(repo);
|
|
159
|
+
return result;
|
|
160
|
+
}
|
|
161
|
+
|
|
146
162
|
// Read a boxed handle (ptr + boxed GType) if `v` is one (tag-checked; no deref of
|
|
147
163
|
// ptr). Returns nullptr when `v` is not a node-gi boxed handle. The returned handle
|
|
148
164
|
// is owned by the External — never free it. Callers that need the GType (boxed
|
|
@@ -472,47 +488,6 @@ static void ThrowExpectedGLibError(Napi::Env env, const char* argName, const std
|
|
|
472
488
|
.ThrowAsJavaScriptException();
|
|
473
489
|
}
|
|
474
490
|
|
|
475
|
-
// Whether `v` is an instance of the L1 `GLib.Error` class — the shape a `catch`
|
|
476
|
-
// around a failed GI invoke hands you, and what `new GLib.Error(domain, code,
|
|
477
|
-
// message)` builds. Recognition is `instanceof` against the class L1 registered
|
|
478
|
-
// alongside its error builder, never duck-typing: a plain object carrying a
|
|
479
|
-
// `code` must still be refused.
|
|
480
|
-
static bool IsL1GLibError(Napi::Env env, Napi::Value v) {
|
|
481
|
-
if (!v.IsObject()) return false;
|
|
482
|
-
NodeGiEnvData* d = EnvData(env);
|
|
483
|
-
if (d == nullptr || d->errorClass == nullptr) return false;
|
|
484
|
-
napi_value cls = nullptr;
|
|
485
|
-
if (napi_get_reference_value(env, d->errorClass, &cls) != napi_ok || cls == nullptr) return false;
|
|
486
|
-
Napi::Value clsValue(env, cls);
|
|
487
|
-
return clsValue.IsFunction() && v.As<Napi::Object>().InstanceOf(clsValue.As<Napi::Function>());
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
// Build a real GError out of an L1 `GLib.Error`, which carries no `GError*` of
|
|
491
|
-
// its own. Throws + returns nullptr when its domain is unusable.
|
|
492
|
-
//
|
|
493
|
-
// `.domainQuark` is the numeric GQuark the engine read off the original GError;
|
|
494
|
-
// `.domain` is its NAME (the node-gi divergence documented in
|
|
495
|
-
// docs/node-gi-gjs-surface.md), which is what an error constructed in JS from a
|
|
496
|
-
// quark name carries. Either resolves to the same quark; a value with neither is
|
|
497
|
-
// not a usable GError — quark 0 would produce an error no `matches()` can ever
|
|
498
|
-
// answer for, so it is refused rather than marshalled into a silent mismatch.
|
|
499
|
-
static GError* ErrorFromL1GLibError(Napi::Env env, Napi::Value v, const char* argName) {
|
|
500
|
-
Napi::Object err = v.As<Napi::Object>();
|
|
501
|
-
Napi::Value quark = err.Get("domainQuark");
|
|
502
|
-
Napi::Value domain = err.Get("domain");
|
|
503
|
-
GQuark gq = 0;
|
|
504
|
-
if (quark.IsNumber()) gq = static_cast<GQuark>(NodeGiToUint32(quark));
|
|
505
|
-
else if (domain.IsString())
|
|
506
|
-
gq = g_quark_from_string(domain.As<Napi::String>().Utf8Value().c_str());
|
|
507
|
-
if (gq == 0) {
|
|
508
|
-
ThrowExpectedGLibError(env, argName, "a GLib.Error with no domain");
|
|
509
|
-
return nullptr;
|
|
510
|
-
}
|
|
511
|
-
Napi::Value message = err.Get("message");
|
|
512
|
-
std::string text = message.IsString() ? message.As<Napi::String>().Utf8Value() : std::string();
|
|
513
|
-
return g_error_new_literal(gq, NodeGiToInt32(err.Get("code")), text.c_str());
|
|
514
|
-
}
|
|
515
|
-
|
|
516
491
|
// "Gtk.Box" for an introspected GType, g_type_name() otherwise (a registerClass
|
|
517
492
|
// subtype has no GI info of its own; its C name is still exact). The gjs twin is
|
|
518
493
|
// GIWrapperBase::format_name() (refs/gjs/gi/wrapperutils.h).
|
|
@@ -541,7 +516,6 @@ bool JsToGIArgument(Napi::Env env, Napi::Value v, GITypeInfo* type, GIArgument*
|
|
|
541
516
|
CreatedClosures* closures,
|
|
542
517
|
CreatedBytes* bytes,
|
|
543
518
|
CreatedValues* values,
|
|
544
|
-
CreatedErrors* errors,
|
|
545
519
|
const char* argName) {
|
|
546
520
|
if (v.IsEmpty()) {
|
|
547
521
|
// Residue of a swallowed napi failure (a fallible Get()/coercion upstream
|
|
@@ -829,43 +803,35 @@ bool JsToGIArgument(Napi::Env env, Napi::Value v, GITypeInfo* type, GIArgument*
|
|
|
829
803
|
out->v_pointer = nullptr;
|
|
830
804
|
return true;
|
|
831
805
|
}
|
|
832
|
-
//
|
|
833
|
-
//
|
|
834
|
-
//
|
|
835
|
-
//
|
|
836
|
-
//
|
|
837
|
-
//
|
|
806
|
+
// There is exactly ONE shape to accept: the boxed handle every GError reaching
|
|
807
|
+
// JS carries, whether it was marshalled out, copied, constructed or thrown
|
|
808
|
+
// (L1's wrapGError puts the same handle on all four). The second arm this used
|
|
809
|
+
// to need — rebuilding a GError from an L1 Error subclass's JS fields — is gone
|
|
810
|
+
// with the subclass itself.
|
|
811
|
+
//
|
|
812
|
+
// Ownership is the ordinary boxed one and TransferBoxedIn supplies it:
|
|
813
|
+
// transfer-none is a borrow, transfer-full hands the callee an independent
|
|
814
|
+
// g_error_copy (g_propagate_error ADOPTS and frees its `src`) so the JS
|
|
815
|
+
// handle's finalizer cannot double-free it. Measured against gjs: after
|
|
816
|
+
// `GLib.propagate_error(e)` the original `e` is still readable there too.
|
|
838
817
|
//
|
|
839
818
|
// Type-safety is UNCONDITIONAL here, unlike the struct/boxed arm's "compare
|
|
840
819
|
// only when both sides carry a registered GType": the expected type is known
|
|
841
820
|
// (G_TYPE_ERROR, always), so a handle without it is a wrong pointer — and a
|
|
842
821
|
// wrong pointer in a GError slot is a wild dereference inside GLib.
|
|
843
822
|
BoxedHandle* h = TryGetBoxedHandle(v);
|
|
844
|
-
if (h
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
h->gtype != G_TYPE_INVALID ? g_type_name(h->gtype)
|
|
848
|
-
: InformalValueTypeName(v));
|
|
849
|
-
return false;
|
|
850
|
-
}
|
|
851
|
-
out->v_pointer = TransferBoxedIn(h, transfer);
|
|
852
|
-
return true;
|
|
823
|
+
if (h == nullptr) {
|
|
824
|
+
ThrowExpectedGLibError(env, argName, InformalValueTypeName(v));
|
|
825
|
+
return false;
|
|
853
826
|
}
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
// application has in hand.
|
|
860
|
-
if (errors != nullptr && IsL1GLibError(env, v)) {
|
|
861
|
-
GError* built = ErrorFromL1GLibError(env, v, argName);
|
|
862
|
-
if (built == nullptr) return false; // ErrorFromL1GLibError already threw
|
|
863
|
-
out->v_pointer = built;
|
|
864
|
-
errors->Record(built, transfer);
|
|
865
|
-
return true;
|
|
827
|
+
if (h->gtype == G_TYPE_INVALID || !g_type_is_a(h->gtype, G_TYPE_ERROR)) {
|
|
828
|
+
ThrowExpectedGLibError(
|
|
829
|
+
env, argName,
|
|
830
|
+
h->gtype != G_TYPE_INVALID ? g_type_name(h->gtype) : InformalValueTypeName(v));
|
|
831
|
+
return false;
|
|
866
832
|
}
|
|
867
|
-
|
|
868
|
-
return
|
|
833
|
+
out->v_pointer = TransferBoxedIn(h, transfer);
|
|
834
|
+
return true;
|
|
869
835
|
}
|
|
870
836
|
default:
|
|
871
837
|
Napi::TypeError::New(
|
|
@@ -995,23 +961,10 @@ Napi::Value GIArgumentToJs(Napi::Env env, GITypeInfo* type, GIArgument* arg,
|
|
|
995
961
|
}
|
|
996
962
|
case GI_TYPE_TAG_ERROR: {
|
|
997
963
|
// A GError-typed value (a `GLib.Error` return like Gtk.GLArea.get_error(),
|
|
998
|
-
// or a GLib.Error.new_literal construct)
|
|
999
|
-
//
|
|
1000
|
-
//
|
|
1001
|
-
|
|
1002
|
-
// no-error case) stays null. WrapBoxed handles transfer: a transfer-none
|
|
1003
|
-
// borrow is g_boxed_copy'd into an owned copy, transfer-full is adopted.
|
|
1004
|
-
GError* gerr = static_cast<GError*>(arg->v_pointer);
|
|
1005
|
-
if (gerr == nullptr) return env.Null();
|
|
1006
|
-
GIRepository* repo = DupDefaultRepository();
|
|
1007
|
-
// GLib is loaded by any namespace require; require defensively anyway so a
|
|
1008
|
-
// bare engine call still resolves the struct info (idempotent, cheap).
|
|
1009
|
-
gi_repository_require(repo, "GLib", "2.0", static_cast<GIRepositoryLoadFlags>(0), nullptr);
|
|
1010
|
-
GIBaseInfo* errInfo = gi_repository_find_by_name(repo, "GLib", "Error");
|
|
1011
|
-
Napi::Value result = WrapBoxed(env, gerr, errInfo, transfer);
|
|
1012
|
-
if (errInfo != nullptr) gi_base_info_unref(errInfo);
|
|
1013
|
-
g_object_unref(repo);
|
|
1014
|
-
return result;
|
|
964
|
+
// or a GLib.Error.new_literal construct). The same handle a THROWN error
|
|
965
|
+
// carries, so L1 builds the same object from both — a NULL GError (the
|
|
966
|
+
// no-error case) stays null.
|
|
967
|
+
return MakeGErrorHandle(env, static_cast<GError*>(arg->v_pointer), transfer);
|
|
1015
968
|
}
|
|
1016
969
|
default:
|
|
1017
970
|
Napi::TypeError::New(env, "Unsupported return type tag " +
|
|
@@ -1861,7 +1814,7 @@ bool JsToAnswerArgument(Napi::Env env, Napi::Value v, GITypeInfo* ti, GITransfer
|
|
|
1861
1814
|
}
|
|
1862
1815
|
std::string held;
|
|
1863
1816
|
return JsToGIArgument(env, v, ti, out, &held, transfer, nullptr, nullptr, nullptr, nullptr,
|
|
1864
|
-
|
|
1817
|
+
argName);
|
|
1865
1818
|
}
|
|
1866
1819
|
|
|
1867
1820
|
} // namespace
|
package/src/repo.cc
CHANGED
|
@@ -13,7 +13,6 @@ void NodeGiEnvDataFinalize(napi_env env, void* data, void* /*hint*/) {
|
|
|
13
13
|
NodeGiEnvData* d = static_cast<NodeGiEnvData*>(data);
|
|
14
14
|
if (d == nullptr) return;
|
|
15
15
|
if (d->errorBuilder != nullptr) napi_delete_reference(env, d->errorBuilder);
|
|
16
|
-
if (d->errorClass != nullptr) napi_delete_reference(env, d->errorClass);
|
|
17
16
|
if (d->templateCallbackResolver != nullptr)
|
|
18
17
|
napi_delete_reference(env, d->templateCallbackResolver);
|
|
19
18
|
if (d->cairoWrappers != nullptr) napi_delete_reference(env, d->cairoWrappers);
|
|
@@ -260,17 +259,52 @@ Napi::Value GetErrorDomain(const Napi::CallbackInfo& info) {
|
|
|
260
259
|
return result;
|
|
261
260
|
}
|
|
262
261
|
|
|
263
|
-
//
|
|
264
|
-
//
|
|
265
|
-
//
|
|
266
|
-
//
|
|
267
|
-
//
|
|
268
|
-
//
|
|
269
|
-
//
|
|
262
|
+
// errorDomainInfo(quark: number) -> { namespace: string, name: string } | null
|
|
263
|
+
// Which introspected enum registered a GError domain quark, so L1 can resolve a
|
|
264
|
+
// thrown error to its DOMAIN CLASS (Gio.IOErrorEnum) by reading that member.
|
|
265
|
+
// getErrorDomain below answers the same relation the other way round, and L1's
|
|
266
|
+
// quark→class map is only populated by it — which is not enough on its own: an
|
|
267
|
+
// application that never named `Gio.IOErrorEnum` has nothing in the map, and the
|
|
268
|
+
// error it catches would land on the base class, breaking exactly the
|
|
269
|
+
// `catch (e) { if (e instanceof Gio.IOErrorEnum) }` idiom. Null for a quark no
|
|
270
|
+
// typelib registers (a private `GLib.quark_from_string` domain).
|
|
271
|
+
Napi::Value ErrorDomainInfo(const Napi::CallbackInfo& info) {
|
|
272
|
+
Napi::Env env = info.Env();
|
|
273
|
+
if (info.Length() < 1 || !info[0].IsNumber()) {
|
|
274
|
+
Napi::TypeError::New(env, "errorDomainInfo(quark: number)").ThrowAsJavaScriptException();
|
|
275
|
+
return env.Null();
|
|
276
|
+
}
|
|
277
|
+
GQuark quark = static_cast<GQuark>(NodeGiToUint32(info[0]));
|
|
278
|
+
if (quark == 0) return env.Null();
|
|
279
|
+
GIRepository* repo = DupDefaultRepository();
|
|
280
|
+
GIEnumInfo* enumInfo = gi_repository_find_by_error_domain(repo, quark);
|
|
281
|
+
Napi::Value result = env.Null();
|
|
282
|
+
if (enumInfo != nullptr) {
|
|
283
|
+
GIBaseInfo* base = reinterpret_cast<GIBaseInfo*>(enumInfo);
|
|
284
|
+
const char* ns = gi_base_info_get_namespace(base);
|
|
285
|
+
const char* name = gi_base_info_get_name(base);
|
|
286
|
+
if (ns != nullptr && name != nullptr) {
|
|
287
|
+
Napi::Object obj = Napi::Object::New(env);
|
|
288
|
+
obj.Set("namespace", Napi::String::New(env, ns));
|
|
289
|
+
obj.Set("name", Napi::String::New(env, name));
|
|
290
|
+
result = obj;
|
|
291
|
+
}
|
|
292
|
+
gi_base_info_unref(base);
|
|
293
|
+
}
|
|
294
|
+
g_object_unref(repo);
|
|
295
|
+
return result;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// setErrorBuilder(builder: (handle) => GLib.Error) -> void
|
|
299
|
+
// Register the L1 error factory the engine calls when a GI invoke fails, so a
|
|
300
|
+
// failed sync call throws the error object of its domain's class. Stored per-env
|
|
301
|
+
// (a napi_ref is env-specific). It takes the GError's boxed HANDLE — the L1 error
|
|
302
|
+
// IS that handle, which is why nothing else needs registering here: the GError
|
|
303
|
+
// IN-arg direction reads the same handle back off the object.
|
|
270
304
|
Napi::Value SetErrorBuilder(const Napi::CallbackInfo& info) {
|
|
271
305
|
Napi::Env env = info.Env();
|
|
272
306
|
if (info.Length() < 1 || !info[0].IsFunction()) {
|
|
273
|
-
Napi::TypeError::New(env, "setErrorBuilder(builder: function
|
|
307
|
+
Napi::TypeError::New(env, "setErrorBuilder(builder: function)")
|
|
274
308
|
.ThrowAsJavaScriptException();
|
|
275
309
|
return env.Undefined();
|
|
276
310
|
}
|
|
@@ -281,12 +315,6 @@ Napi::Value SetErrorBuilder(const Napi::CallbackInfo& info) {
|
|
|
281
315
|
d->errorBuilder = nullptr;
|
|
282
316
|
}
|
|
283
317
|
napi_create_reference(env, info[0], 1, &d->errorBuilder);
|
|
284
|
-
if (d->errorClass != nullptr) {
|
|
285
|
-
napi_delete_reference(env, d->errorClass);
|
|
286
|
-
d->errorClass = nullptr;
|
|
287
|
-
}
|
|
288
|
-
if (info.Length() >= 2 && info[1].IsFunction())
|
|
289
|
-
napi_create_reference(env, info[1], 1, &d->errorClass);
|
|
290
318
|
return env.Undefined();
|
|
291
319
|
}
|
|
292
320
|
|