@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/src/common.h
CHANGED
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
#endif
|
|
36
36
|
|
|
37
37
|
#ifndef _WIN32
|
|
38
|
+
#include <cmath>
|
|
38
39
|
#include <dlfcn.h> // dlopen/dlsym the GtkWidgetClass template API (no GTK link)
|
|
39
40
|
#endif
|
|
40
41
|
#include <napi.h>
|
|
@@ -83,11 +84,6 @@ GIRepository* DupDefaultRepository();
|
|
|
83
84
|
// Init, freed by the instance-data finalizer at env teardown.
|
|
84
85
|
struct NodeGiEnvData {
|
|
85
86
|
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
87
|
// L1-registered resolver for Gtk.Template `<signal handler="…">` callbacks: given
|
|
92
88
|
// (instanceHandle, handlerName) it returns the instance's bound JS method (or
|
|
93
89
|
// undefined). Stored per-env for the same reason as errorBuilder (a napi_ref is
|
|
@@ -160,6 +156,13 @@ extern const napi_type_tag kGObjectHandleTag;
|
|
|
160
156
|
Napi::Value MakeBoxedHandle(Napi::Env env, gpointer ptr, GType gtype, bool owns,
|
|
161
157
|
GIBaseInfo* info = nullptr, bool rawOwned = false);
|
|
162
158
|
Napi::Value WrapVariant(Napi::Env env, GVariant* var, GITransfer transfer);
|
|
159
|
+
// Wrap a `GError*` as the boxed handle L1's `wrapGError` turns into the error
|
|
160
|
+
// object. ONE helper for both directions a GError reaches JS by — a GError-typed
|
|
161
|
+
// return/OUT and the error a failed invoke THREW — because those used to produce
|
|
162
|
+
// two different JS shapes, and sharing the wrap is what keeps them from drifting
|
|
163
|
+
// apart again. Transfer follows the boxed rule: transfer-everything is adopted,
|
|
164
|
+
// transfer-none is copied, so the handle always owns what it frees.
|
|
165
|
+
Napi::Value MakeGErrorHandle(Napi::Env env, GError* error, GITransfer transfer);
|
|
163
166
|
BoxedHandle* TryGetBoxedHandle(Napi::Value v);
|
|
164
167
|
bool TryGetBoxedPtr(Napi::Value v, gpointer* out);
|
|
165
168
|
Napi::Value MakeGTypeHandle(Napi::Env env, GType gtype);
|
|
@@ -278,6 +281,162 @@ inline uint64_t JsValueToUint64(Napi::Value v) {
|
|
|
278
281
|
return n.IsEmpty() ? 0 : static_cast<uint64_t>(n.Int64Value());
|
|
279
282
|
}
|
|
280
283
|
|
|
284
|
+
// IN integers, GJS-exact (refs/gjs/gi/js-value-inl.h, arg-inl.h): an integer tag
|
|
285
|
+
// REFUSES an out-of-range value instead of wrapping it. The rule is not uniform and is
|
|
286
|
+
// copied as is, never made stricter: 8/16-bit run ToInt32 first (so it wraps mod 2^32)
|
|
287
|
+
// and then compare against the tag range; int32 is not checked at all; uint32 and the
|
|
288
|
+
// 64-bit tags refuse what falls outside their range. undefined and a non-finite Number
|
|
289
|
+
// become 0. `shown` receives the text GJS prints for the offending value.
|
|
290
|
+
// kPending: a BigInt/Symbol hit ToNumber, the TypeError is already pending — the caller
|
|
291
|
+
// must stop without throwing a second error.
|
|
292
|
+
enum class NodeGiIntResult { kOk, kOutOfRange, kPending };
|
|
293
|
+
inline NodeGiIntResult NodeGiJsToInteger(Napi::Value v, GITypeTag tag, GIArgument* out,
|
|
294
|
+
std::string* shown) {
|
|
295
|
+
const bool is64 = tag == GI_TYPE_TAG_INT64 || tag == GI_TYPE_TAG_UINT64;
|
|
296
|
+
if (is64 && v.IsBigInt()) {
|
|
297
|
+
bool lossless = false;
|
|
298
|
+
if (tag == GI_TYPE_TAG_INT64) {
|
|
299
|
+
out->v_int64 = v.As<Napi::BigInt>().Int64Value(&lossless);
|
|
300
|
+
} else {
|
|
301
|
+
out->v_uint64 = v.As<Napi::BigInt>().Uint64Value(&lossless);
|
|
302
|
+
}
|
|
303
|
+
if (!lossless && shown != nullptr) *shown = "0";
|
|
304
|
+
return lossless ? NodeGiIntResult::kOk : NodeGiIntResult::kOutOfRange;
|
|
305
|
+
}
|
|
306
|
+
Napi::Number n = v.ToNumber();
|
|
307
|
+
if (n.IsEmpty()) return NodeGiIntResult::kPending;
|
|
308
|
+
double d = n.DoubleValue();
|
|
309
|
+
switch (tag) {
|
|
310
|
+
case GI_TYPE_TAG_INT8:
|
|
311
|
+
case GI_TYPE_TAG_UINT8:
|
|
312
|
+
case GI_TYPE_TAG_INT16:
|
|
313
|
+
case GI_TYPE_TAG_UINT16: {
|
|
314
|
+
int32_t i = n.Int32Value();
|
|
315
|
+
int32_t lo = tag == GI_TYPE_TAG_INT8 ? INT8_MIN : tag == GI_TYPE_TAG_INT16 ? INT16_MIN : 0;
|
|
316
|
+
int32_t hi = tag == GI_TYPE_TAG_INT8 ? INT8_MAX : tag == GI_TYPE_TAG_UINT8 ? UINT8_MAX
|
|
317
|
+
: tag == GI_TYPE_TAG_INT16 ? INT16_MAX : UINT16_MAX;
|
|
318
|
+
if (shown != nullptr) *shown = std::to_string(i);
|
|
319
|
+
if (i < lo || i > hi) return NodeGiIntResult::kOutOfRange;
|
|
320
|
+
if (tag == GI_TYPE_TAG_INT8) out->v_int8 = static_cast<int8_t>(i);
|
|
321
|
+
else if (tag == GI_TYPE_TAG_UINT8) out->v_uint8 = static_cast<uint8_t>(i);
|
|
322
|
+
else if (tag == GI_TYPE_TAG_INT16) out->v_int16 = static_cast<int16_t>(i);
|
|
323
|
+
else out->v_uint16 = static_cast<uint16_t>(i);
|
|
324
|
+
return NodeGiIntResult::kOk;
|
|
325
|
+
}
|
|
326
|
+
case GI_TYPE_TAG_INT32:
|
|
327
|
+
out->v_int32 = n.Int32Value();
|
|
328
|
+
return NodeGiIntResult::kOk;
|
|
329
|
+
case GI_TYPE_TAG_UINT32: {
|
|
330
|
+
bool zero = v.IsUndefined() || (v.IsNumber() && !std::isfinite(d));
|
|
331
|
+
if (zero) {
|
|
332
|
+
out->v_uint32 = 0;
|
|
333
|
+
return NodeGiIntResult::kOk;
|
|
334
|
+
}
|
|
335
|
+
if (shown != nullptr) *shown = std::to_string(d);
|
|
336
|
+
if (std::isnan(d) || d < 0 || d > static_cast<double>(UINT32_MAX))
|
|
337
|
+
return NodeGiIntResult::kOutOfRange;
|
|
338
|
+
out->v_uint32 = static_cast<uint32_t>(d);
|
|
339
|
+
return NodeGiIntResult::kOk;
|
|
340
|
+
}
|
|
341
|
+
case GI_TYPE_TAG_INT64: {
|
|
342
|
+
double t = std::isfinite(d) ? std::trunc(d) : 0;
|
|
343
|
+
if (t < -9223372036854775808.0 || t >= 9223372036854775808.0) {
|
|
344
|
+
if (shown != nullptr)
|
|
345
|
+
*shown = std::to_string(t < 0 ? INT64_MIN : INT64_MAX);
|
|
346
|
+
return NodeGiIntResult::kOutOfRange;
|
|
347
|
+
}
|
|
348
|
+
out->v_int64 = static_cast<int64_t>(t);
|
|
349
|
+
return NodeGiIntResult::kOk;
|
|
350
|
+
}
|
|
351
|
+
case GI_TYPE_TAG_UINT64: {
|
|
352
|
+
double t = std::isfinite(d) ? std::trunc(d) : 0;
|
|
353
|
+
if (t < 0 || t >= 18446744073709551616.0) {
|
|
354
|
+
if (shown != nullptr)
|
|
355
|
+
*shown = std::to_string(t < 0 ? uint64_t{0} : UINT64_MAX);
|
|
356
|
+
return NodeGiIntResult::kOutOfRange;
|
|
357
|
+
}
|
|
358
|
+
out->v_uint64 = static_cast<uint64_t>(t);
|
|
359
|
+
return NodeGiIntResult::kOk;
|
|
360
|
+
}
|
|
361
|
+
default:
|
|
362
|
+
return NodeGiIntResult::kOk;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
enum class NodeGiIntContext { kArgument, kField, kReturnValue };
|
|
367
|
+
inline const char* NodeGiIntTagName(GITypeTag tag) {
|
|
368
|
+
switch (tag) {
|
|
369
|
+
case GI_TYPE_TAG_INT8: return "int8";
|
|
370
|
+
case GI_TYPE_TAG_UINT8: return "uint8";
|
|
371
|
+
case GI_TYPE_TAG_INT16: return "int16";
|
|
372
|
+
case GI_TYPE_TAG_UINT16: return "uint16";
|
|
373
|
+
case GI_TYPE_TAG_INT32: return "int32";
|
|
374
|
+
case GI_TYPE_TAG_UINT32: return "uint32";
|
|
375
|
+
case GI_TYPE_TAG_INT64: return "int64";
|
|
376
|
+
default: return "uint64";
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
// GJS's three texts (arg-cache.cpp / arg.cpp), plain Error.
|
|
380
|
+
inline void NodeGiThrowIntOutOfRange(Napi::Env env, GITypeTag tag, NodeGiIntContext ctx,
|
|
381
|
+
const char* name, const std::string& shown) {
|
|
382
|
+
std::string t = NodeGiIntTagName(tag);
|
|
383
|
+
std::string msg;
|
|
384
|
+
if (ctx == NodeGiIntContext::kArgument) {
|
|
385
|
+
msg = std::string("Argument ") + (name != nullptr ? name : "") + ": value is out of range for " + t;
|
|
386
|
+
} else {
|
|
387
|
+
msg = "value " + shown + " is out of range for " +
|
|
388
|
+
(ctx == NodeGiIntContext::kField ? std::string("Field '") + (name ? name : "") + "'"
|
|
389
|
+
: std::string("Return value")) +
|
|
390
|
+
" (type " + t + ")";
|
|
391
|
+
}
|
|
392
|
+
Napi::Error::New(env, msg).ThrowAsJavaScriptException();
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// IN array elements and GValue int/uint, GJS-exact: these WRAP instead of refusing.
|
|
396
|
+
// An array element goes through js_value_to_c_strict (refs/gjs/gi/arg.cpp:543), which
|
|
397
|
+
// is ToInt32/ToUint32 truncated to the tag for 8/16/32 bits and ToInt64/ToUint64 —
|
|
398
|
+
// modulo 2^64 — for 64 bits; a BigInt takes ToBigInt64/ToBigUint64 (also modulo 2^64)
|
|
399
|
+
// on a 64-bit tag and is a TypeError on a narrower one. kPending: that TypeError is
|
|
400
|
+
// already pending, the caller must stop without throwing a second error.
|
|
401
|
+
inline uint64_t NodeGiWrapToUint64(double d) {
|
|
402
|
+
if (!std::isfinite(d)) return 0;
|
|
403
|
+
double m = std::fmod(std::trunc(d), 18446744073709551616.0);
|
|
404
|
+
return m < 0 ? uint64_t{0} - static_cast<uint64_t>(-m) : static_cast<uint64_t>(m);
|
|
405
|
+
}
|
|
406
|
+
inline NodeGiIntResult NodeGiJsToIntegerWrap(Napi::Value v, GITypeTag tag, GIArgument* out) {
|
|
407
|
+
const bool is64 = tag == GI_TYPE_TAG_INT64 || tag == GI_TYPE_TAG_UINT64;
|
|
408
|
+
if (is64 && v.IsBigInt()) {
|
|
409
|
+
bool lossless = false;
|
|
410
|
+
out->v_uint64 = v.As<Napi::BigInt>().Uint64Value(&lossless);
|
|
411
|
+
return NodeGiIntResult::kOk;
|
|
412
|
+
}
|
|
413
|
+
Napi::Number n = v.ToNumber();
|
|
414
|
+
if (n.IsEmpty()) return NodeGiIntResult::kPending;
|
|
415
|
+
switch (tag) {
|
|
416
|
+
case GI_TYPE_TAG_INT8: out->v_int8 = static_cast<int8_t>(n.Int32Value()); break;
|
|
417
|
+
case GI_TYPE_TAG_UINT8: out->v_uint8 = static_cast<uint8_t>(n.Uint32Value()); break;
|
|
418
|
+
case GI_TYPE_TAG_INT16: out->v_int16 = static_cast<int16_t>(n.Int32Value()); break;
|
|
419
|
+
case GI_TYPE_TAG_UINT16: out->v_uint16 = static_cast<uint16_t>(n.Uint32Value()); break;
|
|
420
|
+
case GI_TYPE_TAG_INT32: out->v_int32 = n.Int32Value(); break;
|
|
421
|
+
case GI_TYPE_TAG_UINT32: out->v_uint32 = n.Uint32Value(); break;
|
|
422
|
+
case GI_TYPE_TAG_INT64:
|
|
423
|
+
case GI_TYPE_TAG_UINT64: out->v_uint64 = NodeGiWrapToUint64(n.DoubleValue()); break;
|
|
424
|
+
default: break;
|
|
425
|
+
}
|
|
426
|
+
return NodeGiIntResult::kOk;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// GValue char/uchar/int64/uint64: refused out of range with gjs_value_to_g_value's own
|
|
430
|
+
// text (refs/gjs/gi/value.cpp:602-690, throw_expect_type), a plain Error. `what` is the
|
|
431
|
+
// "expected" noun gjs prints; the offending value is its JS ToString.
|
|
432
|
+
inline void NodeGiThrowGValueOutOfRange(Napi::Env env, Napi::Value v, const char* what) {
|
|
433
|
+
Napi::String s = v.ToString();
|
|
434
|
+
std::string shown = s.IsEmpty() ? std::string() : s.Utf8Value();
|
|
435
|
+
Napi::Error::New(env, std::string("Wrong type ") + (v.IsBigInt() ? "bigint" : "number") + "; " +
|
|
436
|
+
what + " expected. But it's out of range: " + shown)
|
|
437
|
+
.ThrowAsJavaScriptException();
|
|
438
|
+
}
|
|
439
|
+
|
|
281
440
|
// OUT (WarnIfUnsafe{Int,Uint}64): GJS ALWAYS returns a JS Number for a 64-bit int
|
|
282
441
|
// (never a BigInt) and emits this g_warning when the value falls outside the range
|
|
283
442
|
// a double represents exactly (|v| > 2^53 - 1 == Number.MAX_SAFE_INTEGER), because
|
|
@@ -317,13 +476,13 @@ struct CreatedClosures {
|
|
|
317
476
|
|
|
318
477
|
// ---- C objects this layer SYNTHESISES for one IN argument --------------------
|
|
319
478
|
//
|
|
320
|
-
//
|
|
321
|
-
//
|
|
322
|
-
// number/string for a `GObject.Value` arg
|
|
323
|
-
//
|
|
324
|
-
//
|
|
325
|
-
//
|
|
326
|
-
//
|
|
479
|
+
// Two IN arms build a throw-away C object out of a plain JS value, because the JS
|
|
480
|
+
// side has no handle to hand over: a Uint8Array for a `GLib.Bytes` arg, a
|
|
481
|
+
// number/string for a `GObject.Value` arg. Both carry the SAME lifetime rule,
|
|
482
|
+
// which is why they share one type instead of a second near-identical struct:
|
|
483
|
+
// transfer-none → the callee borrowed (or copied) it, so we release it after the
|
|
484
|
+
// invoke; transfer-full → the callee adopted it, so it is released only when the
|
|
485
|
+
// invoke never ran.
|
|
327
486
|
template <typename T>
|
|
328
487
|
struct CreatedIn {
|
|
329
488
|
std::vector<T*> transferNone; // release after the invoke (callee borrowed/copied)
|
|
@@ -353,17 +512,6 @@ using CreatedBytes = CreatedIn<GBytes>;
|
|
|
353
512
|
// boxed-handle path and is passed through untouched.
|
|
354
513
|
using CreatedValues = CreatedIn<GValue>;
|
|
355
514
|
|
|
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
515
|
// Release a GValue this layer boxed: unset the held value (dropping the ref an
|
|
368
516
|
// object/boxed/string GValue took) and free the struct. A plain `g_free` would
|
|
369
517
|
// leak whatever the GValue owns.
|
|
@@ -390,9 +538,6 @@ GClosure* NodeGiMakeGenericJsClosure(Napi::Env env, Napi::Value fn);
|
|
|
390
538
|
// `values` (optional): enables the JS-value→GValue IN-arg boxing above; nullptr
|
|
391
539
|
// (the default) keeps the previous behaviour (plain value → TypeError) on paths
|
|
392
540
|
// 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
541
|
// `argName` (optional): the introspected argument name, used ONLY in error
|
|
397
542
|
// messages so a refusal reads like gjs's ("Expected type string for argument
|
|
398
543
|
// 'name' but got type number"); nullptr degrades to a message without the name.
|
|
@@ -403,8 +548,8 @@ bool JsToGIArgument(Napi::Env env, Napi::Value v, GITypeInfo* type, GIArgument*
|
|
|
403
548
|
CreatedClosures* closures = nullptr,
|
|
404
549
|
CreatedBytes* bytes = nullptr,
|
|
405
550
|
CreatedValues* values = nullptr,
|
|
406
|
-
|
|
407
|
-
|
|
551
|
+
const char* argName = nullptr,
|
|
552
|
+
NodeGiIntContext intContext = NodeGiIntContext::kArgument);
|
|
408
553
|
|
|
409
554
|
// ---- IN container building -----------------------------------------
|
|
410
555
|
//
|
|
@@ -671,6 +816,7 @@ Napi::Value FindInfo(const Napi::CallbackInfo& info);
|
|
|
671
816
|
Napi::Value GetConstantValue(const Napi::CallbackInfo& info);
|
|
672
817
|
Napi::Value GetEnumValues(const Napi::CallbackInfo& info);
|
|
673
818
|
Napi::Value GetErrorDomain(const Napi::CallbackInfo& info);
|
|
819
|
+
Napi::Value ErrorDomainInfo(const Napi::CallbackInfo& info);
|
|
674
820
|
Napi::Value SetErrorBuilder(const Napi::CallbackInfo& info);
|
|
675
821
|
Napi::Value PrependSearchPath(const Napi::CallbackInfo& info);
|
|
676
822
|
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,8 +516,8 @@ bool JsToGIArgument(Napi::Env env, Napi::Value v, GITypeInfo* type, GIArgument*
|
|
|
541
516
|
CreatedClosures* closures,
|
|
542
517
|
CreatedBytes* bytes,
|
|
543
518
|
CreatedValues* values,
|
|
544
|
-
|
|
545
|
-
|
|
519
|
+
const char* argName,
|
|
520
|
+
NodeGiIntContext intContext) {
|
|
546
521
|
if (v.IsEmpty()) {
|
|
547
522
|
// Residue of a swallowed napi failure (a fallible Get()/coercion upstream
|
|
548
523
|
// failed on a terminating env, or a throwing getter left the exception
|
|
@@ -562,16 +537,24 @@ bool JsToGIArgument(Napi::Env env, Napi::Value v, GITypeInfo* type, GIArgument*
|
|
|
562
537
|
GITypeTag tag = gi_type_info_get_tag(type);
|
|
563
538
|
switch (tag) {
|
|
564
539
|
case GI_TYPE_TAG_BOOLEAN: out->v_boolean = NodeGiToBool(v); return true;
|
|
565
|
-
case GI_TYPE_TAG_INT8:
|
|
566
|
-
case GI_TYPE_TAG_UINT8:
|
|
567
|
-
case GI_TYPE_TAG_INT16:
|
|
568
|
-
case GI_TYPE_TAG_UINT16:
|
|
569
|
-
case GI_TYPE_TAG_INT32:
|
|
570
|
-
case GI_TYPE_TAG_UINT32:
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
540
|
+
case GI_TYPE_TAG_INT8:
|
|
541
|
+
case GI_TYPE_TAG_UINT8:
|
|
542
|
+
case GI_TYPE_TAG_INT16:
|
|
543
|
+
case GI_TYPE_TAG_UINT16:
|
|
544
|
+
case GI_TYPE_TAG_INT32:
|
|
545
|
+
case GI_TYPE_TAG_UINT32:
|
|
546
|
+
case GI_TYPE_TAG_INT64:
|
|
547
|
+
case GI_TYPE_TAG_UINT64: {
|
|
548
|
+
std::string shown;
|
|
549
|
+
switch (NodeGiJsToInteger(v, tag, out, &shown)) {
|
|
550
|
+
case NodeGiIntResult::kOk: return true;
|
|
551
|
+
case NodeGiIntResult::kPending: return false;
|
|
552
|
+
case NodeGiIntResult::kOutOfRange:
|
|
553
|
+
NodeGiThrowIntOutOfRange(env, tag, intContext, argName, shown);
|
|
554
|
+
return false;
|
|
555
|
+
}
|
|
556
|
+
return false;
|
|
557
|
+
}
|
|
575
558
|
case GI_TYPE_TAG_FLOAT: out->v_float = static_cast<float>(NodeGiToDouble(v)); return true;
|
|
576
559
|
case GI_TYPE_TAG_DOUBLE: out->v_double = NodeGiToDouble(v); return true;
|
|
577
560
|
case GI_TYPE_TAG_UTF8:
|
|
@@ -829,43 +812,35 @@ bool JsToGIArgument(Napi::Env env, Napi::Value v, GITypeInfo* type, GIArgument*
|
|
|
829
812
|
out->v_pointer = nullptr;
|
|
830
813
|
return true;
|
|
831
814
|
}
|
|
832
|
-
//
|
|
833
|
-
//
|
|
834
|
-
//
|
|
835
|
-
//
|
|
836
|
-
//
|
|
837
|
-
//
|
|
815
|
+
// There is exactly ONE shape to accept: the boxed handle every GError reaching
|
|
816
|
+
// JS carries, whether it was marshalled out, copied, constructed or thrown
|
|
817
|
+
// (L1's wrapGError puts the same handle on all four). The second arm this used
|
|
818
|
+
// to need — rebuilding a GError from an L1 Error subclass's JS fields — is gone
|
|
819
|
+
// with the subclass itself.
|
|
820
|
+
//
|
|
821
|
+
// Ownership is the ordinary boxed one and TransferBoxedIn supplies it:
|
|
822
|
+
// transfer-none is a borrow, transfer-full hands the callee an independent
|
|
823
|
+
// g_error_copy (g_propagate_error ADOPTS and frees its `src`) so the JS
|
|
824
|
+
// handle's finalizer cannot double-free it. Measured against gjs: after
|
|
825
|
+
// `GLib.propagate_error(e)` the original `e` is still readable there too.
|
|
838
826
|
//
|
|
839
827
|
// Type-safety is UNCONDITIONAL here, unlike the struct/boxed arm's "compare
|
|
840
828
|
// only when both sides carry a registered GType": the expected type is known
|
|
841
829
|
// (G_TYPE_ERROR, always), so a handle without it is a wrong pointer — and a
|
|
842
830
|
// wrong pointer in a GError slot is a wild dereference inside GLib.
|
|
843
831
|
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;
|
|
832
|
+
if (h == nullptr) {
|
|
833
|
+
ThrowExpectedGLibError(env, argName, InformalValueTypeName(v));
|
|
834
|
+
return false;
|
|
853
835
|
}
|
|
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;
|
|
836
|
+
if (h->gtype == G_TYPE_INVALID || !g_type_is_a(h->gtype, G_TYPE_ERROR)) {
|
|
837
|
+
ThrowExpectedGLibError(
|
|
838
|
+
env, argName,
|
|
839
|
+
h->gtype != G_TYPE_INVALID ? g_type_name(h->gtype) : InformalValueTypeName(v));
|
|
840
|
+
return false;
|
|
866
841
|
}
|
|
867
|
-
|
|
868
|
-
return
|
|
842
|
+
out->v_pointer = TransferBoxedIn(h, transfer);
|
|
843
|
+
return true;
|
|
869
844
|
}
|
|
870
845
|
default:
|
|
871
846
|
Napi::TypeError::New(
|
|
@@ -995,23 +970,10 @@ Napi::Value GIArgumentToJs(Napi::Env env, GITypeInfo* type, GIArgument* arg,
|
|
|
995
970
|
}
|
|
996
971
|
case GI_TYPE_TAG_ERROR: {
|
|
997
972
|
// 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;
|
|
973
|
+
// or a GLib.Error.new_literal construct). The same handle a THROWN error
|
|
974
|
+
// carries, so L1 builds the same object from both — a NULL GError (the
|
|
975
|
+
// no-error case) stays null.
|
|
976
|
+
return MakeGErrorHandle(env, static_cast<GError*>(arg->v_pointer), transfer);
|
|
1015
977
|
}
|
|
1016
978
|
default:
|
|
1017
979
|
Napi::TypeError::New(env, "Unsupported return type tag " +
|
|
@@ -1639,21 +1601,36 @@ Napi::Value ReadOutOrReturn(Napi::Env env, GICallableInfo* callable, GITypeInfo*
|
|
|
1639
1601
|
// the element, so it gets a ref (objects) or an independent copy (boxed) instead.
|
|
1640
1602
|
// Throws + returns false on an unsupported element.
|
|
1641
1603
|
static bool ElementToGIArgument(Napi::Env env, GITypeInfo* elem, Napi::Value v, GIArgument* a,
|
|
1642
|
-
GITransfer elemTransfer) {
|
|
1604
|
+
GITransfer elemTransfer, bool wrapIntegers = false) {
|
|
1643
1605
|
memset(a, 0, sizeof(*a));
|
|
1644
1606
|
switch (gi_type_info_get_tag(elem)) {
|
|
1645
1607
|
// Scalar coercions via the terminate-safe helpers (common.h): a swallowed
|
|
1646
1608
|
// napi failure mid worker.terminate() must degrade to a zero, not cascade
|
|
1647
1609
|
// into Error::New(nullptr)'s fatal sites.
|
|
1648
1610
|
case GI_TYPE_TAG_BOOLEAN: a->v_boolean = NodeGiToBool(v); return true;
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
case
|
|
1653
|
-
case
|
|
1654
|
-
case
|
|
1655
|
-
case
|
|
1656
|
-
case
|
|
1611
|
+
// Integers WRAP here, in an array (arg.cpp gjs_array_to_auto_array) — see
|
|
1612
|
+
// NodeGiJsToIntegerWrap (common.h). A GList/GHashTable element keeps the older
|
|
1613
|
+
// coercion: gjs's rule for those is not probed yet.
|
|
1614
|
+
case GI_TYPE_TAG_INT8:
|
|
1615
|
+
case GI_TYPE_TAG_UINT8:
|
|
1616
|
+
case GI_TYPE_TAG_INT16:
|
|
1617
|
+
case GI_TYPE_TAG_UINT16:
|
|
1618
|
+
case GI_TYPE_TAG_INT32:
|
|
1619
|
+
case GI_TYPE_TAG_UINT32:
|
|
1620
|
+
case GI_TYPE_TAG_INT64:
|
|
1621
|
+
case GI_TYPE_TAG_UINT64:
|
|
1622
|
+
if (wrapIntegers)
|
|
1623
|
+
return NodeGiJsToIntegerWrap(v, gi_type_info_get_tag(elem), a) == NodeGiIntResult::kOk;
|
|
1624
|
+
switch (gi_type_info_get_tag(elem)) {
|
|
1625
|
+
case GI_TYPE_TAG_INT8: a->v_int8 = static_cast<gint8>(NodeGiToInt32(v)); return true;
|
|
1626
|
+
case GI_TYPE_TAG_UINT8: a->v_uint8 = static_cast<guint8>(NodeGiToUint32(v)); return true;
|
|
1627
|
+
case GI_TYPE_TAG_INT16: a->v_int16 = static_cast<gint16>(NodeGiToInt32(v)); return true;
|
|
1628
|
+
case GI_TYPE_TAG_UINT16: a->v_uint16 = static_cast<guint16>(NodeGiToUint32(v)); return true;
|
|
1629
|
+
case GI_TYPE_TAG_INT32: a->v_int32 = NodeGiToInt32(v); return true;
|
|
1630
|
+
case GI_TYPE_TAG_UINT32: a->v_uint32 = NodeGiToUint32(v); return true;
|
|
1631
|
+
case GI_TYPE_TAG_INT64: a->v_int64 = JsValueToInt64(v); return true;
|
|
1632
|
+
default: a->v_uint64 = JsValueToUint64(v); return true;
|
|
1633
|
+
}
|
|
1657
1634
|
case GI_TYPE_TAG_FLOAT: a->v_float = static_cast<gfloat>(NodeGiToDouble(v)); return true;
|
|
1658
1635
|
case GI_TYPE_TAG_DOUBLE: a->v_double = NodeGiToDouble(v); return true;
|
|
1659
1636
|
case GI_TYPE_TAG_GTYPE: {
|
|
@@ -1861,7 +1838,10 @@ bool JsToAnswerArgument(Napi::Env env, Napi::Value v, GITypeInfo* ti, GITransfer
|
|
|
1861
1838
|
}
|
|
1862
1839
|
std::string held;
|
|
1863
1840
|
return JsToGIArgument(env, v, ti, out, &held, transfer, nullptr, nullptr, nullptr, nullptr,
|
|
1864
|
-
|
|
1841
|
+
argName,
|
|
1842
|
+
argName != nullptr && strcmp(argName, "return value") == 0
|
|
1843
|
+
? NodeGiIntContext::kReturnValue
|
|
1844
|
+
: NodeGiIntContext::kArgument);
|
|
1865
1845
|
}
|
|
1866
1846
|
|
|
1867
1847
|
} // namespace
|
|
@@ -2320,7 +2300,8 @@ static bool JsToCArray(Napi::Env env, Napi::Value v, GITypeInfo* type, GITransfe
|
|
|
2320
2300
|
continue;
|
|
2321
2301
|
}
|
|
2322
2302
|
GIArgument a;
|
|
2323
|
-
ok = ElementToGIArgument(env, elem, arr.Get(static_cast<uint32_t>(i)), &a, elemTransfer
|
|
2303
|
+
ok = ElementToGIArgument(env, elem, arr.Get(static_cast<uint32_t>(i)), &a, elemTransfer,
|
|
2304
|
+
/*wrapIntegers=*/true);
|
|
2324
2305
|
if (!ok) break;
|
|
2325
2306
|
// Copy the element's storage bytes (LE: the low elemSize bytes of the union
|
|
2326
2307
|
// alias the active field — v_string/v_pointer for pointers, the scalar
|
|
@@ -3020,7 +3001,8 @@ Napi::Value SetBoxedField(const Napi::CallbackInfo& info) {
|
|
|
3020
3001
|
GIArgument arg;
|
|
3021
3002
|
memset(&arg, 0, sizeof(arg));
|
|
3022
3003
|
std::string heldString;
|
|
3023
|
-
if (JsToGIArgument(env, value, ftype, &arg, &heldString, GI_TRANSFER_NOTHING, nullptr
|
|
3004
|
+
if (JsToGIArgument(env, value, ftype, &arg, &heldString, GI_TRANSFER_NOTHING, nullptr,
|
|
3005
|
+
nullptr, nullptr, nullptr, name.c_str(), NodeGiIntContext::kField)) {
|
|
3024
3006
|
if (!gi_field_info_set_field(field, bh->ptr, &arg)) {
|
|
3025
3007
|
Napi::Error::New(env, std::string("Writing field ") + typeName + "." + name + " is not supported")
|
|
3026
3008
|
.ThrowAsJavaScriptException();
|