node-gtk 1.0.0 → 2.1.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/README.md +123 -190
- package/bin/node-gtk.js +31 -0
- package/lib/bootstrap.js +22 -4
- package/lib/index.js +25 -0
- package/lib/loop.js +34 -0
- package/lib/overrides/GLib-2.0.js +12 -9
- package/lib/overrides/Gio-2.0.js +26 -0
- package/lib/overrides/Gtk-3.0.js +17 -12
- package/lib/overrides/Gtk-4.0.js +4 -2
- package/lib/register-class.js +1 -1
- package/package.json +9 -2
- package/scripts/build-test-fixtures.js +237 -0
- package/scripts/ci.sh +5 -3
- package/src/boxed.cc +33 -3
- package/src/boxed.h +13 -0
- package/src/callback.cc +12 -0
- package/src/callback.h +1 -0
- package/src/closure.cc +110 -2
- package/src/function.cc +68 -7
- package/src/function.h +1 -0
- package/src/gi.cc +72 -0
- package/src/gobject.cc +148 -27
- package/src/loop.cc +39 -3
- package/src/loop.h +2 -0
- package/src/type.cc +3 -2
- package/src/value.cc +369 -31
- package/src/value.h +22 -0
- package/tools/README.md +91 -0
- package/tools/generate-types.js +1045 -0
- package/lib/binding/node-v102-linux-x64/node_gtk.node +0 -0
- package/lib/binding/node-v108-linux-x64/node_gtk.node +0 -0
- package/lib/binding/node-v115-linux-x64/node_gtk.node +0 -0
- package/lib/binding/node-v127-linux-x64/node_gtk.node +0 -0
- package/lib/binding/node-v93-linux-x64/node_gtk.node +0 -0
package/src/function.cc
CHANGED
|
@@ -55,7 +55,9 @@ static void* AllocateArgument (GIBaseInfo *arg_info) {
|
|
|
55
55
|
|
|
56
56
|
GIBaseInfo* base_info = g_type_info_get_interface (&arg_type);
|
|
57
57
|
size_t size = Boxed::GetSize (base_info);
|
|
58
|
-
|
|
58
|
+
GType gtype = g_registered_type_info_get_g_type (base_info);
|
|
59
|
+
// Match g_boxed_free's allocator for registered boxed types (#290, #213).
|
|
60
|
+
void* pointer = AllocateBoxed(gtype, size);
|
|
59
61
|
|
|
60
62
|
g_base_info_unref(base_info);
|
|
61
63
|
return pointer;
|
|
@@ -67,6 +69,35 @@ static bool IsMethod (GIBaseInfo *info) {
|
|
|
67
69
|
(flags & GI_FUNCTION_IS_CONSTRUCTOR) == 0);
|
|
68
70
|
}
|
|
69
71
|
|
|
72
|
+
static bool EndsWith (const char *str, const char *suffix) {
|
|
73
|
+
size_t str_len = strlen (str);
|
|
74
|
+
size_t suffix_len = strlen (suffix);
|
|
75
|
+
return str_len >= suffix_len && strcmp (str + str_len - suffix_len, suffix) == 0;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/* True for an instance method that deallocates the instance itself — a *_free
|
|
79
|
+
* or *_unref on a boxed/struct/union. node-gtk's wrapper owns the memory and
|
|
80
|
+
* frees it on GC, so after such a call the wrapper must disown it or GC will
|
|
81
|
+
* double-free (#429). Restricted to boxed-like containers: GObject lifetime is
|
|
82
|
+
* handled separately, and gtk_widget_destroy() etc. don't free the wrapper. */
|
|
83
|
+
static bool FreesInstance (GIFunctionInfo *info) {
|
|
84
|
+
if (!IsMethod (info))
|
|
85
|
+
return false;
|
|
86
|
+
|
|
87
|
+
const char *symbol = g_function_info_get_symbol (info);
|
|
88
|
+
if (symbol == NULL || !(EndsWith (symbol, "_free") || EndsWith (symbol, "_unref")))
|
|
89
|
+
return false;
|
|
90
|
+
|
|
91
|
+
GIBaseInfo *container = g_base_info_get_container (info);
|
|
92
|
+
if (container == NULL)
|
|
93
|
+
return false;
|
|
94
|
+
|
|
95
|
+
GIInfoType type = g_base_info_get_type (container);
|
|
96
|
+
return type == GI_INFO_TYPE_STRUCT
|
|
97
|
+
|| type == GI_INFO_TYPE_UNION
|
|
98
|
+
|| type == GI_INFO_TYPE_BOXED;
|
|
99
|
+
}
|
|
100
|
+
|
|
70
101
|
static bool ShouldSkipReturn(GIBaseInfo *info, GITypeInfo *return_type) {
|
|
71
102
|
return g_type_info_get_tag(return_type) == GI_TYPE_TAG_VOID
|
|
72
103
|
|| g_callable_info_skip_return(info) == TRUE;
|
|
@@ -134,6 +165,7 @@ bool FunctionInfo::Init() {
|
|
|
134
165
|
|
|
135
166
|
is_method = IsMethod(info);
|
|
136
167
|
can_throw = g_callable_info_can_throw_gerror (info);
|
|
168
|
+
frees_instance = FreesInstance(info);
|
|
137
169
|
|
|
138
170
|
n_callable_args = g_callable_info_get_n_args (info);
|
|
139
171
|
n_total_args = n_callable_args;
|
|
@@ -208,14 +240,14 @@ bool FunctionInfo::Init() {
|
|
|
208
240
|
if (closure_i >= 0 && closure_i < n_callable_args)
|
|
209
241
|
call_parameters[closure_i].type = ParameterType::kSKIP;
|
|
210
242
|
|
|
211
|
-
if (destroy_i < i) {
|
|
243
|
+
if (destroy_i >= 0 && destroy_i < i) {
|
|
212
244
|
if (IsDirectionIn(call_parameters[destroy_i].direction))
|
|
213
245
|
n_in_args--;
|
|
214
246
|
if (IsDirectionOut(call_parameters[destroy_i].direction))
|
|
215
247
|
n_out_args--;
|
|
216
248
|
}
|
|
217
249
|
|
|
218
|
-
if (closure_i < i) {
|
|
250
|
+
if (closure_i >= 0 && closure_i < i) {
|
|
219
251
|
if (IsDirectionIn(call_parameters[closure_i].direction))
|
|
220
252
|
n_in_args--;
|
|
221
253
|
if (IsDirectionOut(call_parameters[closure_i].direction))
|
|
@@ -388,16 +420,16 @@ Local<Value> FunctionCall (
|
|
|
388
420
|
}
|
|
389
421
|
else if (param.type == ParameterType::kCALLBACK) {
|
|
390
422
|
Callback *callback;
|
|
391
|
-
|
|
423
|
+
gpointer callable; /* executable trampoline address (see Callback) */
|
|
392
424
|
|
|
393
425
|
if (info[in_arg]->IsNullOrUndefined()) {
|
|
394
|
-
|
|
426
|
+
callable = nullptr;
|
|
395
427
|
callback = nullptr;
|
|
396
428
|
} else {
|
|
397
429
|
GICallableInfo *callback_info = g_type_info_get_interface (&type_info);
|
|
398
430
|
GIScopeType scope_type = g_arg_info_get_scope(&arg_info);
|
|
399
431
|
callback = new Callback(info[in_arg].As<Function>(), callback_info, scope_type);
|
|
400
|
-
|
|
432
|
+
callable = callback->native_address;
|
|
401
433
|
g_base_info_unref (callback_info);
|
|
402
434
|
}
|
|
403
435
|
|
|
@@ -414,7 +446,7 @@ Local<Value> FunctionCall (
|
|
|
414
446
|
callable_arg_values[closure_i].v_pointer = callback;
|
|
415
447
|
}
|
|
416
448
|
|
|
417
|
-
callable_arg_values[i].v_pointer =
|
|
449
|
+
callable_arg_values[i].v_pointer = callable;
|
|
418
450
|
func->call_parameters[i].data.v_pointer = callback;
|
|
419
451
|
}
|
|
420
452
|
|
|
@@ -440,6 +472,25 @@ Local<Value> FunctionCall (
|
|
|
440
472
|
param.data.v_pointer = callable_arg_values[i].v_pointer;
|
|
441
473
|
callable_arg_values[i].v_pointer = ¶m.data;
|
|
442
474
|
}
|
|
475
|
+
// For a transfer-container IN GList/GSList/GHashTable the callee
|
|
476
|
+
// frees the container; snapshot the (caller-owned) element
|
|
477
|
+
// pointers now so they can be freed after the call (#399).
|
|
478
|
+
else if (IsTransferContainerInList(&type_info,
|
|
479
|
+
g_arg_info_get_ownership_transfer(&arg_info), direction)) {
|
|
480
|
+
param.data = {};
|
|
481
|
+
param.data.v_pointer = CaptureTransferContainerElements(
|
|
482
|
+
&type_info, callable_arg_values[i].v_pointer);
|
|
483
|
+
}
|
|
484
|
+
// For a transfer-full IN argument the callee takes ownership.
|
|
485
|
+
// Boxed: hand it a copy so the JS wrapper's own memory isn't
|
|
486
|
+
// double-freed when finalized (#409). GObject: add the reference
|
|
487
|
+
// the callee will own, so it isn't finalized out from under the
|
|
488
|
+
// callee once the wrapper is GC'd (#439).
|
|
489
|
+
else if (direction == GI_DIRECTION_IN
|
|
490
|
+
&& g_arg_info_get_ownership_transfer(&arg_info) == GI_TRANSFER_EVERYTHING) {
|
|
491
|
+
CopyBoxedForTransferFullIn(&type_info, &callable_arg_values[i], param.length);
|
|
492
|
+
RefObjectForTransferFullIn(&type_info, &callable_arg_values[i]);
|
|
493
|
+
}
|
|
443
494
|
}
|
|
444
495
|
|
|
445
496
|
in_arg++;
|
|
@@ -538,6 +589,11 @@ Local<Value> FunctionCall (
|
|
|
538
589
|
delete callback;
|
|
539
590
|
}
|
|
540
591
|
}
|
|
592
|
+
else if (IsTransferContainerInList (&arg_type, transfer, direction)) {
|
|
593
|
+
// The callee already freed the container structure; free only the
|
|
594
|
+
// captured (caller-owned) elements, never the freed container (#399).
|
|
595
|
+
FreeTransferContainerElements (&arg_type, param.data.v_pointer);
|
|
596
|
+
}
|
|
541
597
|
else {
|
|
542
598
|
if (direction == GI_DIRECTION_INOUT || (direction == GI_DIRECTION_OUT && !g_arg_info_is_caller_allocates (&arg_info)))
|
|
543
599
|
FreeGIArgument (&arg_type, (GIArgument*)arg_value.v_pointer, transfer, direction);
|
|
@@ -546,6 +602,11 @@ Local<Value> FunctionCall (
|
|
|
546
602
|
}
|
|
547
603
|
}
|
|
548
604
|
|
|
605
|
+
// If this method freed the instance, drop node-gtk's ownership of it so the
|
|
606
|
+
// GC finalizer won't free it a second time (#429).
|
|
607
|
+
if (func->frees_instance && !didThrow)
|
|
608
|
+
DisownBoxed (info.This());
|
|
609
|
+
|
|
549
610
|
#ifndef __linux__
|
|
550
611
|
delete[] total_arg_values;
|
|
551
612
|
#endif
|
package/src/function.h
CHANGED
package/src/gi.cc
CHANGED
|
@@ -208,6 +208,49 @@ NAN_METHOD(ObjectPropertySetter) {
|
|
|
208
208
|
RETURN(success.ToLocalChecked());
|
|
209
209
|
}
|
|
210
210
|
|
|
211
|
+
// All members of a C union live at offset 0. Some GLib versions (the
|
|
212
|
+
// regression fixed in 2.86.1 — glib#3745) emit a bogus 0xffff offset for union
|
|
213
|
+
// field members in the compiled typelib, so g_field_info_get_field /
|
|
214
|
+
// g_field_info_set_field would read/write far out of bounds and crash. Detect
|
|
215
|
+
// union fields so they can be accessed directly at offset 0 instead (#376).
|
|
216
|
+
static bool FieldIsInUnion (GIFieldInfo *field) {
|
|
217
|
+
GIBaseInfo *container = g_base_info_get_container (field);
|
|
218
|
+
return container != NULL
|
|
219
|
+
&& g_base_info_get_type (container) == GI_INFO_TYPE_UNION;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Whether a field type is a simple (non-pointer) C value that can be read or
|
|
223
|
+
// written by copying its bytes — mirrors what g_field_info_get/set_field
|
|
224
|
+
// accept. Pointer-backed types (strings, nested structs, arrays, ...) return
|
|
225
|
+
// false and are rejected, as before.
|
|
226
|
+
static bool IsSimpleFieldType (GITypeInfo *type_info) {
|
|
227
|
+
switch (g_type_info_get_tag (type_info)) {
|
|
228
|
+
case GI_TYPE_TAG_BOOLEAN:
|
|
229
|
+
case GI_TYPE_TAG_INT8:
|
|
230
|
+
case GI_TYPE_TAG_UINT8:
|
|
231
|
+
case GI_TYPE_TAG_INT16:
|
|
232
|
+
case GI_TYPE_TAG_UINT16:
|
|
233
|
+
case GI_TYPE_TAG_INT32:
|
|
234
|
+
case GI_TYPE_TAG_UINT32:
|
|
235
|
+
case GI_TYPE_TAG_INT64:
|
|
236
|
+
case GI_TYPE_TAG_UINT64:
|
|
237
|
+
case GI_TYPE_TAG_FLOAT:
|
|
238
|
+
case GI_TYPE_TAG_DOUBLE:
|
|
239
|
+
case GI_TYPE_TAG_UNICHAR:
|
|
240
|
+
case GI_TYPE_TAG_GTYPE:
|
|
241
|
+
return true;
|
|
242
|
+
case GI_TYPE_TAG_INTERFACE: {
|
|
243
|
+
GIBaseInfo *iface = g_type_info_get_interface (type_info);
|
|
244
|
+
GIInfoType itype = g_base_info_get_type (iface);
|
|
245
|
+
bool simple = (itype == GI_INFO_TYPE_ENUM || itype == GI_INFO_TYPE_FLAGS);
|
|
246
|
+
g_base_info_unref (iface);
|
|
247
|
+
return simple;
|
|
248
|
+
}
|
|
249
|
+
default:
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
211
254
|
NAN_METHOD(StructFieldSetter) {
|
|
212
255
|
Local<Object> boxedWrapper = info[0].As<Object>();
|
|
213
256
|
Local<Object> fieldInfo = info[1].As<Object>();
|
|
@@ -231,6 +274,17 @@ NAN_METHOD(StructFieldSetter) {
|
|
|
231
274
|
|
|
232
275
|
RETURN (Nan::Undefined());
|
|
233
276
|
|
|
277
|
+
} else if (FieldIsInUnion(field)) {
|
|
278
|
+
|
|
279
|
+
// Union members are at offset 0; write directly to avoid the bogus
|
|
280
|
+
// introspected offset (glib#3745, #376).
|
|
281
|
+
if (IsSimpleFieldType(field_type)) {
|
|
282
|
+
memcpy(boxed, &arg, GNodeJS::GetTypeSize(field_type));
|
|
283
|
+
} else {
|
|
284
|
+
Nan::ThrowError("Unable to set field (complex types not allowed)");
|
|
285
|
+
RETURN (Nan::Undefined());
|
|
286
|
+
}
|
|
287
|
+
|
|
234
288
|
} else {
|
|
235
289
|
|
|
236
290
|
if (g_field_info_set_field(field, boxed, &arg) == FALSE) {
|
|
@@ -286,6 +340,19 @@ NAN_METHOD(StructFieldGetter) {
|
|
|
286
340
|
GNodeJS::ResourceOwnership ownership = GNodeJS::kCopy;
|
|
287
341
|
BaseInfo typeInfo = g_field_info_get_type(*fieldInfo);
|
|
288
342
|
|
|
343
|
+
if (FieldIsInUnion(*fieldInfo)) {
|
|
344
|
+
// Union members are at offset 0; read directly to avoid the bogus
|
|
345
|
+
// introspected offset (glib#3745, #376).
|
|
346
|
+
if (!IsSimpleFieldType(*typeInfo)) {
|
|
347
|
+
Nan::ThrowError("Converting non-primitive fields is not allowed");
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
memset(&value, 0, sizeof(value));
|
|
351
|
+
memcpy(&value, boxed, GNodeJS::GetTypeSize(*typeInfo));
|
|
352
|
+
RETURN(GNodeJS::GIArgumentToV8(*typeInfo, &value, -1, ownership));
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
|
|
289
356
|
if (!g_field_info_get_field(*fieldInfo, boxed, &value)) {
|
|
290
357
|
/* If g_field_info_get_field() failed, this is a non-primitive type */
|
|
291
358
|
|
|
@@ -313,6 +380,10 @@ NAN_METHOD(StartLoop) {
|
|
|
313
380
|
GNodeJS::StartLoop ();
|
|
314
381
|
}
|
|
315
382
|
|
|
383
|
+
NAN_METHOD(IsRunningMicrotasks) {
|
|
384
|
+
info.GetReturnValue().Set(Nan::New<Boolean>(GNodeJS::IsRunningMicrotasks ()));
|
|
385
|
+
}
|
|
386
|
+
|
|
316
387
|
NAN_METHOD(GetBaseClass) {
|
|
317
388
|
auto tpl = GNodeJS::GetBaseClassTemplate ();
|
|
318
389
|
auto fn = Nan::GetFunction (tpl).ToLocalChecked();
|
|
@@ -358,6 +429,7 @@ void InitModule(Local<Object> exports, Local<Value> module, void *priv) {
|
|
|
358
429
|
Nan::Export(exports, "ObjectPropertyGetter", ObjectPropertyGetter);
|
|
359
430
|
Nan::Export(exports, "ObjectPropertySetter", ObjectPropertySetter);
|
|
360
431
|
Nan::Export(exports, "StartLoop", StartLoop);
|
|
432
|
+
Nan::Export(exports, "IsRunningMicrotasks", IsRunningMicrotasks);
|
|
361
433
|
Nan::Export(exports, "GetLoopStack", GetLoopStack);
|
|
362
434
|
Nan::Export(exports, "RegisterClass", RegisterClass);
|
|
363
435
|
Nan::Export(exports, "RegisterVFunc", RegisterVFunc);
|
package/src/gobject.cc
CHANGED
|
@@ -34,7 +34,6 @@ namespace GNodeJS {
|
|
|
34
34
|
static Nan::Persistent<FunctionTemplate> baseTemplate;
|
|
35
35
|
|
|
36
36
|
|
|
37
|
-
static void GObjectDestroyed(const Nan::WeakCallbackInfo<GObject> &data);
|
|
38
37
|
static MaybeLocal<FunctionTemplate> GetClassTemplate(GType gtype);
|
|
39
38
|
static MaybeLocal<Function> GetClass(GType gtype);
|
|
40
39
|
static void StoreVFunc(GType gtype, Callback *callback);
|
|
@@ -59,12 +58,18 @@ static GObject* CreateGObjectFromObject(GType gtype, Local<Value> object) {
|
|
|
59
58
|
|
|
60
59
|
for (int i = 0; i < n_properties; i++) {
|
|
61
60
|
Local<String> name = TO_STRING (Nan::Get(properties, i).ToLocalChecked());
|
|
62
|
-
|
|
61
|
+
// Accept camelCase property names (e.g. iconName) in addition to
|
|
62
|
+
// dashed/underscored ones; GObject canonicalizes '_' to '-' but not
|
|
63
|
+
// camelCase, so convert here (#320). The original spelling is kept so
|
|
64
|
+
// an unknown name is reported as the user wrote it.
|
|
65
|
+
Nan::Utf8String name_original (name);
|
|
66
|
+
char *name_string = Util::ToDashed (*name_original);
|
|
63
67
|
Local<Value> value = Nan::Get(property_hash, name).ToLocalChecked();
|
|
64
68
|
|
|
65
69
|
auto value_spec = g_object_class_find_property (G_OBJECT_CLASS (klass), name_string);
|
|
66
70
|
if (value_spec == NULL) {
|
|
67
|
-
Throw::InvalidPropertyName(
|
|
71
|
+
Throw::InvalidPropertyName(*name_original);
|
|
72
|
+
g_free(name_string);
|
|
68
73
|
goto out;
|
|
69
74
|
}
|
|
70
75
|
|
|
@@ -94,21 +99,66 @@ out:
|
|
|
94
99
|
return gobject;
|
|
95
100
|
}
|
|
96
101
|
|
|
102
|
+
struct GObjectWrapper;
|
|
103
|
+
static void GObjectDestroyedFirstPass(const v8::WeakCallbackInfo<GObjectWrapper> &data);
|
|
104
|
+
static void GObjectDestroyedSecondPass(const v8::WeakCallbackInfo<GObjectWrapper> &data);
|
|
105
|
+
static void GObjectFinalized(gpointer data, GObject *where_the_object_was);
|
|
106
|
+
|
|
107
|
+
struct GObjectWrapper {
|
|
108
|
+
Nan::Persistent<Object> persistent;
|
|
109
|
+
GObject *gobject;
|
|
110
|
+
/* Set to true the moment SetWeak is called. Between that point and the
|
|
111
|
+
* destroy callback actually running, the V8 handle is weak (and, once GC
|
|
112
|
+
* reclaims it, dead). If ToggleNotify fires in that window (because native
|
|
113
|
+
* code adjusts the refcount), touching the persistent crashes. Guard every
|
|
114
|
+
* persistent access with this flag. */
|
|
115
|
+
bool dying = false;
|
|
116
|
+
/* Set in the first-pass weak callback, i.e. the instant GC reclaims the
|
|
117
|
+
* wrapper, before any JS/GTK code resumes. While this is true the
|
|
118
|
+
* persistent is dead but the qdata still points here until the second-pass
|
|
119
|
+
* callback runs; WrapperFromGObject must build a fresh wrapper rather than
|
|
120
|
+
* resurrect this one. */
|
|
121
|
+
bool collected = false;
|
|
122
|
+
};
|
|
123
|
+
|
|
97
124
|
static void ToggleNotify(gpointer user_data, GObject *gobject, gboolean toggle_down) {
|
|
98
125
|
void *data = g_object_get_qdata (gobject, GNodeJS::object_quark());
|
|
99
126
|
|
|
100
127
|
g_assert (data != NULL);
|
|
101
128
|
|
|
102
|
-
auto *
|
|
129
|
+
auto *wrapper = (GObjectWrapper *) data;
|
|
130
|
+
|
|
131
|
+
/* The V8 handle has already been reclaimed by GC (collected) — it is dead
|
|
132
|
+
* and can be made neither weak nor strong. If the object is marshalled
|
|
133
|
+
* again, WrapperFromGObject builds a fresh wrapper. */
|
|
134
|
+
if (wrapper->collected)
|
|
135
|
+
return;
|
|
103
136
|
|
|
104
137
|
if (toggle_down) {
|
|
105
|
-
/* We're dropping from 2 refs to 1 ref
|
|
106
|
-
*
|
|
107
|
-
|
|
138
|
+
/* We're dropping from 2 refs to 1 ref: we are the last holder, so the
|
|
139
|
+
* wrapper may be collected. Install the weak ref (unless it already is). */
|
|
140
|
+
if (wrapper->dying)
|
|
141
|
+
return;
|
|
142
|
+
wrapper->dying = true;
|
|
143
|
+
/* Two-pass weak callback: the first pass runs *during* GC (before any
|
|
144
|
+
* JS/GTK code resumes) and only flips a flag, so WrapperFromGObject can
|
|
145
|
+
* tell a reclaimed wrapper from a live one and never marshals a dead
|
|
146
|
+
* handle to JS. All GObject teardown happens in the second pass — a
|
|
147
|
+
* first-pass callback may not call into GObject. */
|
|
148
|
+
wrapper->persistent.v8::PersistentBase<Object>::SetWeak (
|
|
149
|
+
wrapper, GObjectDestroyedFirstPass, v8::WeakCallbackType::kParameter);
|
|
108
150
|
} else {
|
|
109
|
-
/* We're going from 1 ref to 2 refs
|
|
110
|
-
*
|
|
111
|
-
|
|
151
|
+
/* We're going from 1 ref to 2 refs: something other than us now holds
|
|
152
|
+
* the object, so the wrapper must stay alive (strong) until that ref is
|
|
153
|
+
* dropped again. Reviving here is essential — without it a wrapper that
|
|
154
|
+
* went weak once (e.g. a freshly constructed object at refcount 1) would
|
|
155
|
+
* never become strong again when GTK takes ownership, and GC could then
|
|
156
|
+
* collect a wrapper whose GObject is still in use (notably a subclassed
|
|
157
|
+
* widget owned by GTK, losing its overridden vfuncs and instance state). */
|
|
158
|
+
if (!wrapper->dying)
|
|
159
|
+
return;
|
|
160
|
+
wrapper->dying = false;
|
|
161
|
+
wrapper->persistent.ClearWeak ();
|
|
112
162
|
}
|
|
113
163
|
}
|
|
114
164
|
|
|
@@ -117,8 +167,10 @@ static void AssociateGObject(Local<Object> object, GObject *gobject, GType gtype
|
|
|
117
167
|
|
|
118
168
|
SET_OBJECT_GTYPE(object, gtype);
|
|
119
169
|
|
|
120
|
-
auto *
|
|
121
|
-
|
|
170
|
+
auto *wrapper = new GObjectWrapper();
|
|
171
|
+
wrapper->gobject = gobject;
|
|
172
|
+
wrapper->persistent.Reset(object);
|
|
173
|
+
g_object_set_qdata (gobject, GNodeJS::object_quark(), wrapper);
|
|
122
174
|
|
|
123
175
|
// Because we can't sink floating ref and add toggle ref at the same time,
|
|
124
176
|
// first sink the floating ref, add the toggle ref, and then release the
|
|
@@ -126,6 +178,18 @@ static void AssociateGObject(Local<Object> object, GObject *gobject, GType gtype
|
|
|
126
178
|
g_object_ref_sink (gobject);
|
|
127
179
|
g_object_add_toggle_ref (gobject, ToggleNotify, NULL);
|
|
128
180
|
g_object_unref (gobject);
|
|
181
|
+
|
|
182
|
+
// The toggle ref above is supposed to keep the GObject alive for as long as
|
|
183
|
+
// the wrapper exists. A weak ref guards against the case where it doesn't —
|
|
184
|
+
// e.g. a JS-subclassed instance whose refcount is driven to 0 from the GTK
|
|
185
|
+
// side while we still hold the toggle ref: GObjectFinalized then clears the
|
|
186
|
+
// dangling pointer so the destroy callbacks never touch freed memory.
|
|
187
|
+
g_object_weak_ref (gobject, GObjectFinalized, wrapper);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
static void GObjectFinalized(gpointer data, GObject *where_the_object_was) {
|
|
191
|
+
auto *wrapper = (GObjectWrapper *) data;
|
|
192
|
+
wrapper->gobject = NULL;
|
|
129
193
|
}
|
|
130
194
|
|
|
131
195
|
static void GObjectConstructor(const FunctionCallbackInfo<Value> &info) {
|
|
@@ -177,18 +241,71 @@ static void GObjectConstructor(const FunctionCallbackInfo<Value> &info) {
|
|
|
177
241
|
}
|
|
178
242
|
}
|
|
179
243
|
|
|
180
|
-
static void
|
|
181
|
-
|
|
244
|
+
static void GObjectDestroyedFirstPass(const v8::WeakCallbackInfo<GObjectWrapper> &data) {
|
|
245
|
+
GObjectWrapper *wrapper = data.GetParameter ();
|
|
246
|
+
|
|
247
|
+
/* This runs *during* GC, where it is not legal to call into V8 (beyond
|
|
248
|
+
* resetting the handle, which the two-pass contract requires) or into
|
|
249
|
+
* GObject — the GObject is not safe to touch here, and doing so crashes in
|
|
250
|
+
* g_type_check_instance_is_fundamentally_a. So only flip a flag and reset
|
|
251
|
+
* the handle; the real teardown is deferred to the second pass.
|
|
252
|
+
*
|
|
253
|
+
* The flag lets WrapperFromGObject distinguish a reclaimed wrapper (whose
|
|
254
|
+
* persistent is now dead) from a live one during the window before the
|
|
255
|
+
* second pass nulls the qdata, so it builds a fresh wrapper instead of
|
|
256
|
+
* handing the dead handle to JS — which crashed on first property access. */
|
|
257
|
+
wrapper->collected = true;
|
|
258
|
+
wrapper->persistent.Reset ();
|
|
259
|
+
|
|
260
|
+
data.SetSecondPassCallback (GObjectDestroyedSecondPass);
|
|
261
|
+
}
|
|
182
262
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
263
|
+
static gboolean GObjectTeardownIdle(gpointer data) {
|
|
264
|
+
GObjectWrapper *wrapper = (GObjectWrapper *) data;
|
|
265
|
+
GObject *gobject = wrapper->gobject;
|
|
266
|
+
|
|
267
|
+
/* If the GObject was already finalized out from under us, GObjectFinalized
|
|
268
|
+
* cleared the pointer; there is nothing left to detach or unref. */
|
|
269
|
+
if (gobject != NULL) {
|
|
270
|
+
/* Drop the weak ref first so removing the toggle ref (which may finalize
|
|
271
|
+
* the object) doesn't re-enter GObjectFinalized. */
|
|
272
|
+
g_object_weak_unref (gobject, GObjectFinalized, wrapper);
|
|
273
|
+
|
|
274
|
+
/* Only detach the qdata if it still points at us — WrapperFromGObject
|
|
275
|
+
* may have resurrected this GObject with a fresh wrapper while we were
|
|
276
|
+
* pending, and we must not clobber it. */
|
|
277
|
+
if (g_object_get_qdata (gobject, GNodeJS::object_quark()) == wrapper)
|
|
278
|
+
g_object_set_qdata (gobject, GNodeJS::object_quark(), NULL);
|
|
279
|
+
|
|
280
|
+
/* Dropping the last toggle ref disposes the object, and GTK's dispose
|
|
281
|
+
* synchronously emits signals (e.g. ::destroy) into still-connected
|
|
282
|
+
* node-gtk closures — i.e. it re-enters arbitrary JS. That is only legal
|
|
283
|
+
* here because we run from a GLib idle on the main loop, not from the GC
|
|
284
|
+
* second-pass callback that scheduled us (see GObjectDestroyedSecondPass). */
|
|
285
|
+
g_object_remove_toggle_ref (gobject, &ToggleNotify, NULL);
|
|
286
|
+
}
|
|
186
287
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
288
|
+
delete wrapper;
|
|
289
|
+
return G_SOURCE_REMOVE;
|
|
290
|
+
}
|
|
190
291
|
|
|
191
|
-
|
|
292
|
+
static void GObjectDestroyedSecondPass(const v8::WeakCallbackInfo<GObjectWrapper> &data) {
|
|
293
|
+
GObjectWrapper *wrapper = data.GetParameter ();
|
|
294
|
+
|
|
295
|
+
/* Defer the actual teardown to a main-loop idle instead of running it here.
|
|
296
|
+
* This callback fires from V8's InvokeSecondPassPhantomCallbacks *during* a
|
|
297
|
+
* garbage collection. Dropping the toggle ref can take the GObject's refcount
|
|
298
|
+
* to zero, and GTK's dispose then emits signals into node-gtk closures,
|
|
299
|
+
* re-entering JS (Nan::Call) — which crashes when invoked mid-GC. Running the
|
|
300
|
+
* teardown from a GLib idle moves the ref drop (and any disposal/signal
|
|
301
|
+
* emission it triggers) to a point where calling into JS is safe again.
|
|
302
|
+
*
|
|
303
|
+
* The GObject stays alive across the window because we still hold the toggle
|
|
304
|
+
* ref; the wrapper (with its now-reset persistent and collected=true) is kept
|
|
305
|
+
* until the idle deletes it. WrapperFromGObject already handles a resurrected
|
|
306
|
+
* GObject during this window by building a fresh wrapper, and the idle's
|
|
307
|
+
* qdata check above won't clobber it. */
|
|
308
|
+
g_idle_add (GObjectTeardownIdle, wrapper);
|
|
192
309
|
}
|
|
193
310
|
|
|
194
311
|
static void GObjectClassDestroyed(const Nan::WeakCallbackInfo<GType> &info) {
|
|
@@ -333,8 +450,7 @@ static void StoreVFunc(GType gtype, Callback *callback) {
|
|
|
333
450
|
static void DestroyVFuncs(GType gtype) {
|
|
334
451
|
/* Destroy vfunc list, if any */
|
|
335
452
|
GSList *list = (GSList *) g_type_get_qdata (gtype, GNodeJS::vfuncs_quark());
|
|
336
|
-
GSList *item = list;
|
|
337
|
-
while ((item = g_slist_next (item)) != NULL) {
|
|
453
|
+
for (GSList *item = list; item != NULL; item = item->next) {
|
|
338
454
|
auto callback = (Callback *) item->data;
|
|
339
455
|
delete callback;
|
|
340
456
|
}
|
|
@@ -652,10 +768,15 @@ Local<Value> WrapperFromGObject(GObject *gobject) {
|
|
|
652
768
|
void *data = g_object_get_qdata (gobject, GNodeJS::object_quark());
|
|
653
769
|
|
|
654
770
|
if (data) {
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
771
|
+
auto *wrapper = (GObjectWrapper *) data;
|
|
772
|
+
/* Reuse the existing wrapper unless GC has already reclaimed it (its
|
|
773
|
+
* persistent is dead and only awaiting the second-pass teardown). In
|
|
774
|
+
* that case fall through and build a fresh wrapper; the stale one's
|
|
775
|
+
* second pass is guarded so it won't clobber the new qdata. */
|
|
776
|
+
if (!wrapper->collected) {
|
|
777
|
+
auto obj = New<Object> (wrapper->persistent);
|
|
778
|
+
return obj;
|
|
779
|
+
}
|
|
659
780
|
}
|
|
660
781
|
|
|
661
782
|
GType gtype = G_OBJECT_TYPE(gobject);
|
package/src/loop.cc
CHANGED
|
@@ -26,6 +26,8 @@ static Nan::Persistent<Array> loopStack(Nan::New<Array> ());
|
|
|
26
26
|
struct uv_loop_source {
|
|
27
27
|
GSource source;
|
|
28
28
|
uv_loop_t *loop;
|
|
29
|
+
gpointer fd_tag;
|
|
30
|
+
bool fd_polled;
|
|
29
31
|
};
|
|
30
32
|
|
|
31
33
|
static gboolean loop_source_prepare (GSource *base, int *timeout) {
|
|
@@ -45,6 +47,24 @@ static gboolean loop_source_prepare (GSource *base, int *timeout) {
|
|
|
45
47
|
|
|
46
48
|
bool loop_alive = uv_loop_alive (source->loop);
|
|
47
49
|
|
|
50
|
+
/* Toggle whether GLib polls uv's backend fd. When the loop is dead we must
|
|
51
|
+
* stop polling it: an *unref'd* but still-active uv handle (e.g. the async
|
|
52
|
+
* eventfd left signaling by emscripten/WASM runtimes such as web-tree-sitter,
|
|
53
|
+
* or by worker_threads) keeps the backend epoll fd perpetually readable.
|
|
54
|
+
* uv_loop_alive() reports the loop dead (unref'd handles don't count), so we
|
|
55
|
+
* intend to sleep here, but a polled-and-ready fd makes GLib's poll() return
|
|
56
|
+
* immediately every iteration -> loop_source_dispatch() busy-spins at 100%
|
|
57
|
+
* CPU, starving GTK. Masking the fd lets GLib actually block until a GTK
|
|
58
|
+
* source wakes us; we restore polling as soon as the loop is alive again. */
|
|
59
|
+
#if !OS_WINDOWS
|
|
60
|
+
if (source->fd_tag != NULL && loop_alive != source->fd_polled) {
|
|
61
|
+
g_source_modify_unix_fd (&source->source, source->fd_tag,
|
|
62
|
+
loop_alive ? (GIOCondition) (G_IO_IN | G_IO_OUT | G_IO_ERR)
|
|
63
|
+
: (GIOCondition) 0);
|
|
64
|
+
source->fd_polled = loop_alive;
|
|
65
|
+
}
|
|
66
|
+
#endif
|
|
67
|
+
|
|
48
68
|
/* If the loop is dead, we can simply sleep forever until a GTK+ source
|
|
49
69
|
* (presumably) wakes us back up again. */
|
|
50
70
|
if (!loop_alive)
|
|
@@ -92,13 +112,15 @@ static GSourceFuncs uv_loop_source_funcs = {
|
|
|
92
112
|
static GSource *loop_source_new (uv_loop_t *loop) {
|
|
93
113
|
struct uv_loop_source *source = (struct uv_loop_source *) g_source_new (&uv_loop_source_funcs, sizeof (*source));
|
|
94
114
|
source->loop = loop;
|
|
115
|
+
source->fd_tag = NULL;
|
|
116
|
+
source->fd_polled = true;
|
|
95
117
|
#if OS_WINDOWS
|
|
96
118
|
// FIXME
|
|
97
119
|
// https://github.com/nodejs/node/issues/36015
|
|
98
120
|
#else
|
|
99
|
-
g_source_add_unix_fd (&source->source,
|
|
100
|
-
|
|
101
|
-
|
|
121
|
+
source->fd_tag = g_source_add_unix_fd (&source->source,
|
|
122
|
+
uv_backend_fd (loop),
|
|
123
|
+
(GIOCondition) (G_IO_IN | G_IO_OUT | G_IO_ERR));
|
|
102
124
|
#endif
|
|
103
125
|
return &source->source;
|
|
104
126
|
}
|
|
@@ -145,6 +167,20 @@ void CallMicrotaskHandlers () {
|
|
|
145
167
|
#endif
|
|
146
168
|
}
|
|
147
169
|
|
|
170
|
+
bool IsRunningMicrotasks() {
|
|
171
|
+
/* True while V8 is draining the microtask queue. Under ES modules the
|
|
172
|
+
* top-level body executes as a microtask, so a synchronous blocking call
|
|
173
|
+
* (e.g. g_main_loop_run) made from it nests inside this drain. V8 refuses
|
|
174
|
+
* nested microtask checkpoints, so any Promise/async continuation queued
|
|
175
|
+
* by user code is stuck until the blocking call returns. Callers use this
|
|
176
|
+
* to detect that situation and defer the blocking call to a macrotask so
|
|
177
|
+
* the module's top-level microtask can return and the queue can drain.
|
|
178
|
+
*
|
|
179
|
+
* - https://github.com/romgrk/node-gtk/issues/442
|
|
180
|
+
*/
|
|
181
|
+
return MicrotasksScope::IsRunningMicrotasks(Isolate::GetCurrent());
|
|
182
|
+
}
|
|
183
|
+
|
|
148
184
|
void StartLoop() {
|
|
149
185
|
GSource *source = loop_source_new (uv_default_loop ());
|
|
150
186
|
g_source_attach (source, NULL);
|
package/src/loop.h
CHANGED
package/src/type.cc
CHANGED
|
@@ -15,11 +15,12 @@ char *GetInfoName (GIBaseInfo* info) {
|
|
|
15
15
|
|
|
16
16
|
char* name = g_strdup (info_name);
|
|
17
17
|
|
|
18
|
-
GIBaseInfo *parent;
|
|
19
|
-
while (
|
|
18
|
+
GIBaseInfo *parent = g_base_info_get_container (info);
|
|
19
|
+
while (parent != NULL) {
|
|
20
20
|
char *new_name = g_strconcat (g_base_info_get_name(parent), ".", name, NULL);
|
|
21
21
|
g_free (name);
|
|
22
22
|
name = new_name;
|
|
23
|
+
parent = g_base_info_get_container (parent);
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
char *new_name = g_strconcat (g_base_info_get_namespace(info), ".", name, NULL);
|