node-gtk 4.0.1 → 4.1.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/binding.gyp +1 -0
- package/lib/bootstrap.js +92 -1
- package/lib/module.js +6 -9
- package/lib/native.js +13 -3
- package/package.json +24 -7
- package/scripts/ci.sh +2 -5
- package/src/boxed.cc +13 -3
- package/src/callback.cc +25 -9
- package/src/function.cc +1 -4
- package/src/fundamental.cc +26 -0
- package/src/gi.cc +101 -0
- package/src/gi.h +21 -0
- package/src/gobject.cc +74 -28
- package/src/gobject.h +5 -0
- package/src/toggle_queue.cc +44 -0
- package/src/toggle_queue.h +53 -0
- package/src/value.cc +20 -3
- package/src/value.h +10 -0
package/binding.gyp
CHANGED
package/lib/bootstrap.js
CHANGED
|
@@ -2,9 +2,21 @@
|
|
|
2
2
|
* bootstrap.js
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
const
|
|
5
|
+
const camelCaseRaw = require('lodash.camelcase')
|
|
6
6
|
const internal = require('./native.js')
|
|
7
7
|
|
|
8
|
+
// Method/field/property names repeat heavily across classes (get_name,
|
|
9
|
+
// set_visible, ...): memoize the case conversion.
|
|
10
|
+
const camelCaseCache = new Map()
|
|
11
|
+
function camelCase(name) {
|
|
12
|
+
let result = camelCaseCache.get(name)
|
|
13
|
+
if (result === undefined) {
|
|
14
|
+
result = camelCaseRaw(name)
|
|
15
|
+
camelCaseCache.set(name, result)
|
|
16
|
+
}
|
|
17
|
+
return result
|
|
18
|
+
}
|
|
19
|
+
|
|
8
20
|
// The bootstrap from C here contains functions and methods for each object,
|
|
9
21
|
// namespaced with underscores. See gi.cc for more information.
|
|
10
22
|
const GI = internal.Bootstrap();
|
|
@@ -15,6 +27,7 @@ module.exports = {
|
|
|
15
27
|
GI,
|
|
16
28
|
makeInfo,
|
|
17
29
|
getInfoName,
|
|
30
|
+
defineLazyInfos,
|
|
18
31
|
}
|
|
19
32
|
|
|
20
33
|
// The GIRepository API is fairly poor, and contains methods on classes,
|
|
@@ -341,6 +354,84 @@ function applyInterfaceMethods(constructor, interfaceRefs) {
|
|
|
341
354
|
|
|
342
355
|
internal.SetInterfaceMethodsApplier(applyInterfaceMethods)
|
|
343
356
|
|
|
357
|
+
/*
|
|
358
|
+
* Lazy type materialization. Namespaces expose thousands of top-level infos
|
|
359
|
+
* (6,600+ for Gtk-4.0 with its dependencies) but an app touches a few dozen:
|
|
360
|
+
* giRequire() defines one lazy accessor per info instead of building every
|
|
361
|
+
* class eagerly, and the first access runs makeInfo() and replaces the
|
|
362
|
+
* accessor with the result. Types reached from C before JS ever names them (a
|
|
363
|
+
* method return value, a signal argument) are materialized through the C++
|
|
364
|
+
* template-creation hook (SetTypeMaterializer below), so their prototypes are
|
|
365
|
+
* decorated before any wrapper is handed out.
|
|
366
|
+
*/
|
|
367
|
+
|
|
368
|
+
const materializing = new Set()
|
|
369
|
+
|
|
370
|
+
function defineLazyInfos(module, ns) {
|
|
371
|
+
// One native call per namespace: a flat [name, infoType, index, ...] array
|
|
372
|
+
// of the infos makeInfo() can handle (enumerating via GI.* costs ~3 FFI
|
|
373
|
+
// round-trips per info, 48ms for Gtk-4.0 + dependencies).
|
|
374
|
+
const entries = internal.GetInfoEntries(ns)
|
|
375
|
+
|
|
376
|
+
for (let i = 0; i < entries.length; i += 3) {
|
|
377
|
+
const type = entries[i + 1]
|
|
378
|
+
const name = type === GI.InfoType.FUNCTION ? camelCase(entries[i]) : entries[i]
|
|
379
|
+
defineLazyInfo(module, ns, name, entries[i + 2])
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function defineLazyInfo(module, ns, name, index) {
|
|
384
|
+
const key = ns + '.' + name
|
|
385
|
+
|
|
386
|
+
Object.defineProperty(module, name, {
|
|
387
|
+
configurable: true,
|
|
388
|
+
enumerable: true,
|
|
389
|
+
get() {
|
|
390
|
+
/* Re-entered while materializing: the C++ hook fires for the class
|
|
391
|
+
* makeInfo() is currently building, and MakeBoxedClass() consults the
|
|
392
|
+
* module cache for the G_TYPE_NONE struct it is building. Report
|
|
393
|
+
* undefined; the outer call installs the final value. */
|
|
394
|
+
if (materializing.has(key))
|
|
395
|
+
return undefined
|
|
396
|
+
materializing.add(key)
|
|
397
|
+
try {
|
|
398
|
+
const repo = GI.Repository_get_default()
|
|
399
|
+
const info = GI.Repository_get_info.call(repo, ns, index)
|
|
400
|
+
const value = makeInfo(info)
|
|
401
|
+
Object.defineProperty(module, name, {
|
|
402
|
+
configurable: true, enumerable: true, writable: true, value,
|
|
403
|
+
})
|
|
404
|
+
return value
|
|
405
|
+
} finally {
|
|
406
|
+
materializing.delete(key)
|
|
407
|
+
}
|
|
408
|
+
},
|
|
409
|
+
/* Plain assignments must keep working (getInterface() and the overrides
|
|
410
|
+
* write straight into the module). */
|
|
411
|
+
set(value) {
|
|
412
|
+
Object.defineProperty(module, name, {
|
|
413
|
+
configurable: true, enumerable: true, writable: true, value,
|
|
414
|
+
})
|
|
415
|
+
},
|
|
416
|
+
})
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/*
|
|
420
|
+
* Called from C++ when a class template is first created for an
|
|
421
|
+
* introspectable type (GetClassTemplate/GetBoxedFunction/
|
|
422
|
+
* GetFundamentalTemplate), so that a type reached from C first has its
|
|
423
|
+
* prototype decorated. Triggering the accessor is enough; the accessor makes
|
|
424
|
+
* this idempotent and re-entrancy-safe.
|
|
425
|
+
*/
|
|
426
|
+
function materializeType(ns, name) {
|
|
427
|
+
const module = moduleCache[ns]
|
|
428
|
+
if (module === undefined)
|
|
429
|
+
return
|
|
430
|
+
void module[name]
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
internal.SetTypeMaterializer(materializeType)
|
|
434
|
+
|
|
344
435
|
function makeBoxed(info) {
|
|
345
436
|
if (getType(info) == GI.InfoType.UNION) {
|
|
346
437
|
return makeUnion(info)
|
package/lib/module.js
CHANGED
|
@@ -7,7 +7,7 @@ const util = require('util')
|
|
|
7
7
|
const readdir = util.promisify(fs.readdir)
|
|
8
8
|
|
|
9
9
|
const internal = require('./native.js')
|
|
10
|
-
const { GI,
|
|
10
|
+
const { GI, defineLazyInfos } = require('./bootstrap.js')
|
|
11
11
|
|
|
12
12
|
const moduleCache = internal.GetModuleCache();
|
|
13
13
|
|
|
@@ -37,14 +37,11 @@ function giRequire(ns, version) {
|
|
|
37
37
|
|
|
38
38
|
loadDependencies(ns, version)
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
if (item !== undefined)
|
|
46
|
-
module[getInfoName(info)] = item
|
|
47
|
-
}
|
|
40
|
+
// Top-level infos materialize on first access (see defineLazyInfos in
|
|
41
|
+
// bootstrap.js): building every class of a namespace and its dependencies
|
|
42
|
+
// eagerly used to cost ~160ms for Gtk-4.0, for the few dozen classes a
|
|
43
|
+
// typical app touches.
|
|
44
|
+
defineLazyInfos(module, ns)
|
|
48
45
|
|
|
49
46
|
// Apply overrides, if present
|
|
50
47
|
let override
|
package/lib/native.js
CHANGED
|
@@ -2,12 +2,22 @@
|
|
|
2
2
|
* native.js
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
const binary = require('@mapbox/node-pre-gyp')
|
|
6
5
|
const path = require('path')
|
|
7
6
|
const fs = require('fs')
|
|
8
7
|
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
// Resolve the compiled addon directly rather than through node-pre-gyp:
|
|
9
|
+
// requiring node-pre-gyp costs ~16ms of startup for what amounts to
|
|
10
|
+
// evaluating the module_path template of package.json (`binary.find`). Keep
|
|
11
|
+
// it as a fallback for any layout the shorthand doesn't cover (e.g. a
|
|
12
|
+
// non-node runtime with a different ABI naming scheme).
|
|
13
|
+
const bindingName = `node-v${process.versions.modules}-${process.platform}-${process.arch}`
|
|
14
|
+
let bindingPath = path.join(__dirname, 'binding', bindingName, 'node_gtk.node')
|
|
15
|
+
|
|
16
|
+
if (!fs.existsSync(bindingPath)) {
|
|
17
|
+
const binary = require('@mapbox/node-pre-gyp')
|
|
18
|
+
const packagePath = path.resolve(path.join(__dirname, '../package.json'))
|
|
19
|
+
bindingPath = binary.find(packagePath)
|
|
20
|
+
}
|
|
11
21
|
|
|
12
22
|
// On Windows, the prebuilt binary ships with its whole GTK runtime bundled in
|
|
13
23
|
// the same directory as the .node (DLLs, GObject-Introspection typelibs, and
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-gtk",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.1.1",
|
|
4
4
|
"description": "GNOME Gtk+ bindings for NodeJS",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"exports": {
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
},
|
|
52
52
|
"homepage": "https://github.com/romgrk/node-gtk#readme",
|
|
53
53
|
"dependencies": {
|
|
54
|
-
"@mapbox/node-pre-gyp": "^
|
|
54
|
+
"@mapbox/node-pre-gyp": "^2.0.3",
|
|
55
55
|
"lodash.camelcase": "4.3.0",
|
|
56
56
|
"lodash.isequal": "4.5.0",
|
|
57
57
|
"lodash.snakecase": "^4.1.1",
|
|
@@ -62,11 +62,26 @@
|
|
|
62
62
|
},
|
|
63
63
|
"devDependencies": {
|
|
64
64
|
"assert": "^1.5.0",
|
|
65
|
-
"aws-sdk": "^2.
|
|
65
|
+
"aws-sdk": "^2.1692.0",
|
|
66
66
|
"chalk": "^2.4.2",
|
|
67
|
-
"mocha": "^7.
|
|
68
|
-
"nid-parser": "0.0.5"
|
|
69
|
-
|
|
67
|
+
"mocha": "^11.7.0",
|
|
68
|
+
"nid-parser": "0.0.5"
|
|
69
|
+
},
|
|
70
|
+
"overrides": {
|
|
71
|
+
"nid-parser": {
|
|
72
|
+
"js-yaml": "^3.15.0",
|
|
73
|
+
"extend": "^3.0.2"
|
|
74
|
+
},
|
|
75
|
+
"optimist": {
|
|
76
|
+
"minimist": "^1.2.8"
|
|
77
|
+
},
|
|
78
|
+
"aws-sdk": {
|
|
79
|
+
"uuid": "^11.1.1"
|
|
80
|
+
},
|
|
81
|
+
"mocha": {
|
|
82
|
+
"diff": "^8.0.3",
|
|
83
|
+
"serialize-javascript": "^7.0.5"
|
|
84
|
+
}
|
|
70
85
|
},
|
|
71
86
|
"files": [
|
|
72
87
|
"/bin",
|
|
@@ -74,7 +89,9 @@
|
|
|
74
89
|
"/src",
|
|
75
90
|
"/scripts",
|
|
76
91
|
"/tools",
|
|
77
|
-
"binding.gyp"
|
|
92
|
+
"binding.gyp",
|
|
93
|
+
"!lib/binding",
|
|
94
|
+
"!**/*.node"
|
|
78
95
|
],
|
|
79
96
|
"binary": {
|
|
80
97
|
"module_name": "node_gtk",
|
package/scripts/ci.sh
CHANGED
|
@@ -46,12 +46,9 @@ function npm_test() {
|
|
|
46
46
|
# fixture build does not run automatically; do it here. Best-effort:
|
|
47
47
|
# marshalling tests skip if fixtures cannot be produced on macOS.
|
|
48
48
|
npm run build:test-fixtures || true;
|
|
49
|
-
npx mocha
|
|
50
|
-
--skip=callback \
|
|
51
|
-
tests/__run__.js
|
|
49
|
+
NODE_GTK_TEST_SKIP=callback npx mocha tests/__run__.js
|
|
52
50
|
else
|
|
53
|
-
xvfb-run -a npm test
|
|
54
|
-
--skip=callback;
|
|
51
|
+
NODE_GTK_TEST_SKIP=callback xvfb-run -a npm test;
|
|
55
52
|
fi;
|
|
56
53
|
}
|
|
57
54
|
|
package/src/boxed.cc
CHANGED
|
@@ -409,6 +409,14 @@ Local<Function> GetBoxedFunction(GIBaseInfo *info, GType gtype) {
|
|
|
409
409
|
|
|
410
410
|
g_type_set_qdata(gtype, GNodeJS::function_quark(), persistent);
|
|
411
411
|
|
|
412
|
+
/* Modules hold lazy accessors: a registered boxed type reached from C
|
|
413
|
+
* first (method return value, signal argument) must be materialized so
|
|
414
|
+
* makeBoxed() in JS attaches its methods/fields before a wrapper is
|
|
415
|
+
* handed out. Fired after the qdata above so the re-entrant
|
|
416
|
+
* MakeBoxedClass() from JS finds this same function. Idempotent and
|
|
417
|
+
* re-entrancy-safe on the JS side. */
|
|
418
|
+
GNodeJS::MaterializeType(info);
|
|
419
|
+
|
|
412
420
|
return fn;
|
|
413
421
|
}
|
|
414
422
|
|
|
@@ -430,10 +438,12 @@ Local<Function> MakeBoxedClass(GIBaseInfo *info) {
|
|
|
430
438
|
if (Nan::HasOwnProperty(moduleCache, ns).FromMaybe(false)) {
|
|
431
439
|
auto module = TO_OBJECT (Nan::Get(moduleCache, ns).ToLocalChecked());
|
|
432
440
|
|
|
433
|
-
|
|
434
|
-
|
|
441
|
+
/* The Get can run a lazy accessor. It returns the materialized
|
|
442
|
+
* class, or undefined when re-entered from the accessor's own
|
|
443
|
+
* makeBoxed() call — fall through and build the class then. */
|
|
444
|
+
auto constructor = Nan::Get(module, name).ToLocalChecked();
|
|
445
|
+
if (constructor->IsFunction())
|
|
435
446
|
return Local<Function>::Cast (constructor);
|
|
436
|
-
}
|
|
437
447
|
}
|
|
438
448
|
}
|
|
439
449
|
|
package/src/callback.cc
CHANGED
|
@@ -23,6 +23,9 @@ namespace GNodeJS {
|
|
|
23
23
|
|
|
24
24
|
static guint callbackLevel = 0;
|
|
25
25
|
static GSList* notifiedCallbacks = NULL;
|
|
26
|
+
/* DestroyNotify can fire on any thread (e.g. a GTask freeing its callback
|
|
27
|
+
* data on a pool thread); the list must not race the JS thread's AsyncFree. */
|
|
28
|
+
static GMutex notifiedCallbacksLock;
|
|
26
29
|
|
|
27
30
|
static Local<Object> GetSelfInstance(GIArgument **args) {
|
|
28
31
|
return WrapperFromGObject((GObject *)args[0]->v_pointer).As<Object>();
|
|
@@ -66,26 +69,27 @@ Callback::~Callback() {
|
|
|
66
69
|
* we can't free the resources here. They'll be freed in Callback::AsyncFree.
|
|
67
70
|
*/
|
|
68
71
|
void Callback::DestroyNotify (void* user_data) {
|
|
72
|
+
g_mutex_lock (¬ifiedCallbacksLock);
|
|
69
73
|
notifiedCallbacks = g_slist_prepend (notifiedCallbacks, user_data);
|
|
74
|
+
g_mutex_unlock (¬ifiedCallbacksLock);
|
|
70
75
|
}
|
|
71
76
|
|
|
72
77
|
/**
|
|
73
78
|
* Frees the callbacks that have been destroy-notified
|
|
74
79
|
*/
|
|
75
80
|
void Callback::AsyncFree () {
|
|
76
|
-
if (
|
|
81
|
+
if (callbackLevel > 0)
|
|
77
82
|
return;
|
|
78
83
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
delete callback;
|
|
84
|
+
g_mutex_lock (¬ifiedCallbacksLock);
|
|
85
|
+
GSList* list = notifiedCallbacks;
|
|
86
|
+
notifiedCallbacks = NULL;
|
|
87
|
+
g_mutex_unlock (¬ifiedCallbacksLock);
|
|
84
88
|
|
|
85
|
-
|
|
86
|
-
|
|
89
|
+
for (GSList* current = list; current != NULL; current = current->next)
|
|
90
|
+
delete static_cast<Callback*>(current->data);
|
|
87
91
|
|
|
88
|
-
|
|
92
|
+
g_slist_free (list);
|
|
89
93
|
}
|
|
90
94
|
|
|
91
95
|
/**
|
|
@@ -220,6 +224,18 @@ void Callback::Execute (GIArgument *result, GIArgument **args, Callback *callbac
|
|
|
220
224
|
Throw::InvalidReturnValue (&return_type_info, jsReturnValue);
|
|
221
225
|
goto out;
|
|
222
226
|
}
|
|
227
|
+
|
|
228
|
+
// When the callback's return value is transfer-full, the C caller takes
|
|
229
|
+
// ownership of it (e.g. GtkTreeListModelCreateModelFunc → GListModel).
|
|
230
|
+
// V8ToGIArgument only unwrapped the pointer, leaving node-gtk holding
|
|
231
|
+
// just its toggle ref; without the owning reference the caller "owns"
|
|
232
|
+
// that toggle ref, so no toggle-up fires, the wrapper stays weak, and
|
|
233
|
+
// once GC collects it the object is finalized while the caller still
|
|
234
|
+
// uses it — a use-after-free (e.g. gtk_tree_list_model_finalize
|
|
235
|
+
// disconnecting its items-changed handler from a freed child model).
|
|
236
|
+
// This is the return counterpart of the transfer-full IN argument.
|
|
237
|
+
if (g_callable_info_get_caller_owns(callback->info) == GI_TRANSFER_EVERYTHING)
|
|
238
|
+
TakeOwnershipForTransferFull(&return_type_info, result, -1);
|
|
223
239
|
}
|
|
224
240
|
|
|
225
241
|
// TODO: assess transferness of arguments & check if we need to free them
|
package/src/function.cc
CHANGED
|
@@ -527,10 +527,7 @@ Local<Value> FunctionCall (
|
|
|
527
527
|
// GskRenderNode): add the reference the callee will own (#468).
|
|
528
528
|
else if (direction == GI_DIRECTION_IN
|
|
529
529
|
&& g_arg_info_get_ownership_transfer(&arg_info) == GI_TRANSFER_EVERYTHING) {
|
|
530
|
-
|
|
531
|
-
RefObjectForTransferFullIn(&type_info, &callable_arg_values[i]);
|
|
532
|
-
RefFundamentalForTransferFullIn(&type_info, &callable_arg_values[i]);
|
|
533
|
-
RefVariantForTransferFullIn(&type_info, &callable_arg_values[i]);
|
|
530
|
+
TakeOwnershipForTransferFull(&type_info, &callable_arg_values[i], param.length);
|
|
534
531
|
}
|
|
535
532
|
}
|
|
536
533
|
|
package/src/fundamental.cc
CHANGED
|
@@ -239,6 +239,16 @@ static Local<FunctionTemplate> GetFundamentalTemplate (GIObjectInfo *info, GType
|
|
|
239
239
|
}
|
|
240
240
|
}
|
|
241
241
|
|
|
242
|
+
/* The parent recursion above runs JS (each ancestor's template fires the
|
|
243
|
+
* type materializer below), so JS may have re-entered here and created
|
|
244
|
+
* this very template already. Keep that one: its function is the one JS
|
|
245
|
+
* has started decorating. */
|
|
246
|
+
data = g_type_get_qdata (gtype, GNodeJS::template_quark ());
|
|
247
|
+
if (data) {
|
|
248
|
+
auto *persistent = (Persistent<FunctionTemplate> *) data;
|
|
249
|
+
return New<FunctionTemplate> (*persistent);
|
|
250
|
+
}
|
|
251
|
+
|
|
242
252
|
auto *persistentTpl = new Persistent<FunctionTemplate> (tpl);
|
|
243
253
|
auto *persistentFn = new Persistent<Function> (Nan::GetFunction (tpl).ToLocalChecked ());
|
|
244
254
|
persistentTpl->SetWeak (
|
|
@@ -247,6 +257,11 @@ static Local<FunctionTemplate> GetFundamentalTemplate (GIObjectInfo *info, GType
|
|
|
247
257
|
g_type_set_qdata (gtype, GNodeJS::template_quark (), persistentTpl);
|
|
248
258
|
g_type_set_qdata (gtype, GNodeJS::function_quark (), persistentFn);
|
|
249
259
|
|
|
260
|
+
/* Modules hold lazy accessors: a fundamental type reached from C first
|
|
261
|
+
* must be materialized so makeObject() in JS attaches its methods before
|
|
262
|
+
* a wrapper is handed out (see gi.h). */
|
|
263
|
+
GNodeJS::MaterializeType (info);
|
|
264
|
+
|
|
250
265
|
return tpl;
|
|
251
266
|
}
|
|
252
267
|
|
|
@@ -405,6 +420,17 @@ static Local<FunctionTemplate> GetVariantTemplate () {
|
|
|
405
420
|
g_type_set_qdata (gtype, GNodeJS::template_quark (), persistentTpl);
|
|
406
421
|
g_type_set_qdata (gtype, GNodeJS::function_quark (), persistentFn);
|
|
407
422
|
|
|
423
|
+
/* Modules hold lazy accessors: a variant reached from C first (e.g. a
|
|
424
|
+
* signal argument, #465) must have GLib.Variant materialized so
|
|
425
|
+
* makeBoxed() in JS attaches its methods before a wrapper is handed out. */
|
|
426
|
+
if (g_irepository_is_registered (NULL, "GLib", NULL)) {
|
|
427
|
+
GIBaseInfo *variant_info = g_irepository_find_by_name (NULL, "GLib", "Variant");
|
|
428
|
+
if (variant_info) {
|
|
429
|
+
GNodeJS::MaterializeType (variant_info);
|
|
430
|
+
g_base_info_unref (variant_info);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
408
434
|
return tpl;
|
|
409
435
|
}
|
|
410
436
|
|
package/src/gi.cc
CHANGED
|
@@ -32,9 +32,37 @@ namespace GNodeJS {
|
|
|
32
32
|
|
|
33
33
|
Nan::Persistent<Object> moduleCache(Nan::New<Object>());
|
|
34
34
|
|
|
35
|
+
GThread *js_thread = NULL;
|
|
36
|
+
|
|
35
37
|
Local<Object> GetModuleCache() {
|
|
36
38
|
return Nan::New<Object>(GNodeJS::moduleCache);
|
|
37
39
|
}
|
|
40
|
+
|
|
41
|
+
static Nan::Persistent<v8::Function> typeMaterializer;
|
|
42
|
+
|
|
43
|
+
void SetTypeMaterializerInternal(Local<v8::Function> fn) {
|
|
44
|
+
typeMaterializer.Reset(fn);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
void MaterializeType(GIBaseInfo *info) {
|
|
48
|
+
if (typeMaterializer.IsEmpty() || info == NULL)
|
|
49
|
+
return;
|
|
50
|
+
|
|
51
|
+
Local<v8::Function> fn = Nan::New<v8::Function>(typeMaterializer);
|
|
52
|
+
Local<Value> argv[] = {
|
|
53
|
+
UTF8(g_base_info_get_namespace(info)),
|
|
54
|
+
UTF8(g_base_info_get_name(info)),
|
|
55
|
+
};
|
|
56
|
+
Nan::TryCatch tryCatch;
|
|
57
|
+
Nan::Call(fn, Nan::GetCurrentContext()->Global(), 2, argv);
|
|
58
|
+
if (tryCatch.HasCaught()) {
|
|
59
|
+
Nan::Utf8String message(tryCatch.Exception());
|
|
60
|
+
g_warning("node-gtk: could not materialize type %s.%s: %s",
|
|
61
|
+
g_base_info_get_namespace(info),
|
|
62
|
+
g_base_info_get_name(info),
|
|
63
|
+
*message);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
38
66
|
}
|
|
39
67
|
|
|
40
68
|
|
|
@@ -121,6 +149,71 @@ NAN_METHOD(Bootstrap) {
|
|
|
121
149
|
info.GetReturnValue().Set(module_obj);
|
|
122
150
|
}
|
|
123
151
|
|
|
152
|
+
/*
|
|
153
|
+
* Enumerate a loaded namespace's top-level infos in one native call, so that
|
|
154
|
+
* lib/module.js can define lazy accessors without paying ~3 FFI round-trips
|
|
155
|
+
* per info (48ms for Gtk-4.0 + dependencies, vs ~1ms here). Returns a flat
|
|
156
|
+
* array of [name, infoType, index, ...] triplets. Infos that makeInfo() cannot
|
|
157
|
+
* handle (callbacks, gtype structs, etc.) are filtered out here, mirroring the
|
|
158
|
+
* `item !== undefined` check the eager loop used to do.
|
|
159
|
+
*/
|
|
160
|
+
NAN_METHOD(GetInfoEntries) {
|
|
161
|
+
Nan::Utf8String ns(info[0]);
|
|
162
|
+
GIRepository *repo = g_irepository_get_default();
|
|
163
|
+
|
|
164
|
+
int n = g_irepository_get_n_infos(repo, *ns);
|
|
165
|
+
Local<Array> result = Nan::New<Array>();
|
|
166
|
+
uint32_t position = 0;
|
|
167
|
+
|
|
168
|
+
for (int i = 0; i < n; i++) {
|
|
169
|
+
BaseInfo baseInfo(g_irepository_get_info(repo, *ns, i));
|
|
170
|
+
GIInfoType type = baseInfo.type();
|
|
171
|
+
|
|
172
|
+
switch (type) {
|
|
173
|
+
case GI_INFO_TYPE_FUNCTION:
|
|
174
|
+
case GI_INFO_TYPE_BOXED:
|
|
175
|
+
case GI_INFO_TYPE_ENUM:
|
|
176
|
+
case GI_INFO_TYPE_FLAGS:
|
|
177
|
+
case GI_INFO_TYPE_OBJECT:
|
|
178
|
+
case GI_INFO_TYPE_INTERFACE:
|
|
179
|
+
case GI_INFO_TYPE_CONSTANT:
|
|
180
|
+
case GI_INFO_TYPE_UNION:
|
|
181
|
+
break;
|
|
182
|
+
case GI_INFO_TYPE_STRUCT:
|
|
183
|
+
if (g_struct_info_is_gtype_struct(baseInfo.info()))
|
|
184
|
+
continue;
|
|
185
|
+
break;
|
|
186
|
+
default:
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/* Register the GType with the GObject runtime NOW, even though the JS
|
|
191
|
+
* class stays lazy. The eager loop did this as a side effect
|
|
192
|
+
* (MakeObjectClass/MakeBoxedClass call get_g_type() for every class)
|
|
193
|
+
* and code depends on it: by-name lookups such as
|
|
194
|
+
* GObject.typeFromName('GFileInfo') or type names in GtkBuilder XML
|
|
195
|
+
* must resolve without JS ever touching the class. Registration is a
|
|
196
|
+
* few µs per type; class initialization stays lazy either way. */
|
|
197
|
+
switch (type) {
|
|
198
|
+
case GI_INFO_TYPE_STRUCT:
|
|
199
|
+
case GI_INFO_TYPE_BOXED:
|
|
200
|
+
case GI_INFO_TYPE_UNION:
|
|
201
|
+
case GI_INFO_TYPE_OBJECT:
|
|
202
|
+
case GI_INFO_TYPE_INTERFACE:
|
|
203
|
+
g_registered_type_info_get_g_type((GIRegisteredTypeInfo *) baseInfo.info());
|
|
204
|
+
break;
|
|
205
|
+
default:
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
Nan::Set(result, position++, UTF8(baseInfo.name()));
|
|
210
|
+
Nan::Set(result, position++, Nan::New<v8::Int32>(type));
|
|
211
|
+
Nan::Set(result, position++, Nan::New<v8::Int32>(i));
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
info.GetReturnValue().Set(result);
|
|
215
|
+
}
|
|
216
|
+
|
|
124
217
|
NAN_METHOD(GetConstantValue) {
|
|
125
218
|
GIBaseInfo *gi_info = (GIBaseInfo *) GNodeJS::PointerFromWrapper (info[0]);
|
|
126
219
|
GITypeInfo *type_info = g_constant_info_get_type(gi_info);
|
|
@@ -418,6 +511,10 @@ NAN_METHOD(SetInterfaceMethodsApplier) {
|
|
|
418
511
|
GNodeJS::SetInterfaceMethodsApplier(info);
|
|
419
512
|
}
|
|
420
513
|
|
|
514
|
+
NAN_METHOD(SetTypeMaterializer) {
|
|
515
|
+
GNodeJS::SetTypeMaterializerInternal(info[0].As<v8::Function>());
|
|
516
|
+
}
|
|
517
|
+
|
|
421
518
|
NAN_METHOD(RegisterClass) {
|
|
422
519
|
GNodeJS::ObjectClass::RegisterClass(info);
|
|
423
520
|
}
|
|
@@ -431,6 +528,8 @@ NAN_METHOD(CallVFunc) {
|
|
|
431
528
|
}
|
|
432
529
|
|
|
433
530
|
void InitModule(Local<Object> exports, Local<Value> module, void *priv) {
|
|
531
|
+
GNodeJS::js_thread = g_thread_self();
|
|
532
|
+
|
|
434
533
|
GNodeJS::AsyncCallEnvironment::Initialize();
|
|
435
534
|
|
|
436
535
|
Nan::Export(exports, "Bootstrap", Bootstrap);
|
|
@@ -438,6 +537,8 @@ void InitModule(Local<Object> exports, Local<Value> module, void *priv) {
|
|
|
438
537
|
Nan::Export(exports, "GetBaseClass", GetBaseClass);
|
|
439
538
|
Nan::Export(exports, "GetTypeSize", GetTypeSize);
|
|
440
539
|
Nan::Export(exports, "GetConstantValue", GetConstantValue);
|
|
540
|
+
Nan::Export(exports, "GetInfoEntries", GetInfoEntries);
|
|
541
|
+
Nan::Export(exports, "SetTypeMaterializer", SetTypeMaterializer);
|
|
441
542
|
Nan::Export(exports, "MakeBoxedClass", MakeBoxedClass);
|
|
442
543
|
Nan::Export(exports, "MakeObjectClass", MakeObjectClass);
|
|
443
544
|
Nan::Export(exports, "MakeFunction", MakeFunction);
|
package/src/gi.h
CHANGED
|
@@ -24,8 +24,29 @@ namespace GNodeJS {
|
|
|
24
24
|
|
|
25
25
|
extern Nan::Persistent<Object> moduleCache;
|
|
26
26
|
|
|
27
|
+
/*
|
|
28
|
+
* The thread running V8/libuv (recorded at module init). V8 objects — global
|
|
29
|
+
* handles in particular — may only be touched from this thread.
|
|
30
|
+
*/
|
|
31
|
+
extern GThread *js_thread;
|
|
32
|
+
|
|
27
33
|
Local<Object> GetModuleCache();
|
|
28
34
|
|
|
35
|
+
/*
|
|
36
|
+
* Lazy type materialization: modules are populated with lazy accessors
|
|
37
|
+
* (lib/module.js), and prototypes only get their methods/properties when a
|
|
38
|
+
* class is first touched. Types can also be reached from C first (a method
|
|
39
|
+
* return value, a signal argument): the template-creation paths in
|
|
40
|
+
* gobject.cc/boxed.cc/fundamental.cc call MaterializeType so the JS side
|
|
41
|
+
* decorates the prototype before any wrapper is handed out. The callback is
|
|
42
|
+
* installed from bootstrap.js via SetTypeMaterializer and is idempotent and
|
|
43
|
+
* re-entrancy-safe on the JS side.
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
void MaterializeType(GIBaseInfo *info);
|
|
47
|
+
|
|
48
|
+
void SetTypeMaterializerInternal(Local<v8::Function> fn);
|
|
49
|
+
|
|
29
50
|
|
|
30
51
|
|
|
31
52
|
/*
|
package/src/gobject.cc
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
#include "gi.h"
|
|
12
12
|
#include "gobject.h"
|
|
13
13
|
#include "macros.h"
|
|
14
|
+
#include "toggle_queue.h"
|
|
14
15
|
#include "util.h"
|
|
15
16
|
#include "value.h"
|
|
16
17
|
|
|
@@ -133,10 +134,28 @@ struct GObjectWrapper {
|
|
|
133
134
|
bool collected = false;
|
|
134
135
|
};
|
|
135
136
|
|
|
136
|
-
|
|
137
|
+
/* Reconcile the wrapper's persistent with the state the object's current
|
|
138
|
+
* refcount calls for. Main thread only — called inline by ToggleNotify on the
|
|
139
|
+
* JS thread, and by toggleQueue's drain for deferred off-thread notifications.
|
|
140
|
+
*
|
|
141
|
+
* Weak when the toggle ref is the only reference left: we are the last
|
|
142
|
+
* holder, so the wrapper may be collected. The two-pass weak callback's first
|
|
143
|
+
* pass runs *during* GC (before any JS/GTK code resumes) and only flips a
|
|
144
|
+
* flag, so WrapperFromGObject can tell a reclaimed wrapper from a live one
|
|
145
|
+
* and never marshals a dead handle to JS. All GObject teardown happens in the
|
|
146
|
+
* second pass — a first-pass callback may not call into GObject.
|
|
147
|
+
*
|
|
148
|
+
* Strong otherwise: something other than us holds the object, so the wrapper
|
|
149
|
+
* must stay alive until that ref is dropped again. Reviving is essential —
|
|
150
|
+
* without it a wrapper that went weak once (e.g. a freshly constructed object
|
|
151
|
+
* at refcount 1) would never become strong again when GTK takes ownership,
|
|
152
|
+
* and GC could then collect a wrapper whose GObject is still in use (notably
|
|
153
|
+
* a subclassed widget owned by GTK, losing its overridden vfuncs and instance
|
|
154
|
+
* state). */
|
|
155
|
+
void SynchronizeToggleState(GObject *gobject) {
|
|
137
156
|
void *data = g_object_get_qdata (gobject, GNodeJS::object_quark());
|
|
138
|
-
|
|
139
|
-
|
|
157
|
+
if (data == NULL)
|
|
158
|
+
return;
|
|
140
159
|
|
|
141
160
|
auto *wrapper = (GObjectWrapper *) data;
|
|
142
161
|
|
|
@@ -146,34 +165,37 @@ static void ToggleNotify(gpointer user_data, GObject *gobject, gboolean toggle_d
|
|
|
146
165
|
if (wrapper->collected)
|
|
147
166
|
return;
|
|
148
167
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
return;
|
|
168
|
+
bool only_toggle_ref =
|
|
169
|
+
g_atomic_int_get ((const gint *) &gobject->ref_count) <= 1;
|
|
170
|
+
|
|
171
|
+
if (only_toggle_ref && !wrapper->dying) {
|
|
154
172
|
wrapper->dying = true;
|
|
155
|
-
/* Two-pass weak callback: the first pass runs *during* GC (before any
|
|
156
|
-
* JS/GTK code resumes) and only flips a flag, so WrapperFromGObject can
|
|
157
|
-
* tell a reclaimed wrapper from a live one and never marshals a dead
|
|
158
|
-
* handle to JS. All GObject teardown happens in the second pass — a
|
|
159
|
-
* first-pass callback may not call into GObject. */
|
|
160
173
|
wrapper->persistent.v8::PersistentBase<Object>::SetWeak (
|
|
161
174
|
wrapper, GObjectDestroyedFirstPass, v8::WeakCallbackType::kParameter);
|
|
162
|
-
} else {
|
|
163
|
-
/* We're going from 1 ref to 2 refs: something other than us now holds
|
|
164
|
-
* the object, so the wrapper must stay alive (strong) until that ref is
|
|
165
|
-
* dropped again. Reviving here is essential — without it a wrapper that
|
|
166
|
-
* went weak once (e.g. a freshly constructed object at refcount 1) would
|
|
167
|
-
* never become strong again when GTK takes ownership, and GC could then
|
|
168
|
-
* collect a wrapper whose GObject is still in use (notably a subclassed
|
|
169
|
-
* widget owned by GTK, losing its overridden vfuncs and instance state). */
|
|
170
|
-
if (!wrapper->dying)
|
|
171
|
-
return;
|
|
175
|
+
} else if (!only_toggle_ref && wrapper->dying) {
|
|
172
176
|
wrapper->dying = false;
|
|
173
177
|
wrapper->persistent.ClearWeak ();
|
|
174
178
|
}
|
|
175
179
|
}
|
|
176
180
|
|
|
181
|
+
/* Fires synchronously on whatever thread crosses the 1<->2 refcount boundary
|
|
182
|
+
* — e.g. GLib's worker thread dropping the ref it held on a GSubprocess while
|
|
183
|
+
* waiting for the child to exit. V8 global handles may only be touched from
|
|
184
|
+
* the JS thread: a SetWeak/ClearWeak from a worker raced the scavenger's
|
|
185
|
+
* weak-handle processing and corrupted the global-handle list (fatal "Check
|
|
186
|
+
* failed: Heap::InFromPage(heap_object)" in a later scavenge, or random
|
|
187
|
+
* SIGSEGV). Off-thread notifications are deferred to the main context; the
|
|
188
|
+
* notified direction is not forwarded because the deferred reconciliation
|
|
189
|
+
* re-derives it from the refcount (see toggle_queue.h). */
|
|
190
|
+
static void ToggleNotify(gpointer user_data, GObject *gobject, gboolean toggle_down) {
|
|
191
|
+
if (G_UNLIKELY (g_thread_self () != GNodeJS::js_thread)) {
|
|
192
|
+
toggleQueue.Synchronize (gobject);
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
SynchronizeToggleState (gobject);
|
|
197
|
+
}
|
|
198
|
+
|
|
177
199
|
static void AssociateGObject(Local<Object> object, GObject *gobject, GType gtype) {
|
|
178
200
|
Nan::SetInternalFieldPointer(object, 0, gobject);
|
|
179
201
|
|
|
@@ -202,6 +224,9 @@ static void AssociateGObject(Local<Object> object, GObject *gobject, GType gtype
|
|
|
202
224
|
static void GObjectFinalized(gpointer data, GObject *where_the_object_was) {
|
|
203
225
|
auto *wrapper = (GObjectWrapper *) data;
|
|
204
226
|
wrapper->gobject = NULL;
|
|
227
|
+
/* A deferred off-thread toggle may still be queued for this object; the
|
|
228
|
+
* drain must not touch freed memory. */
|
|
229
|
+
toggleQueue.Cancel (where_the_object_was);
|
|
205
230
|
}
|
|
206
231
|
|
|
207
232
|
static void GObjectConstructor(const FunctionCallbackInfo<Value> &info) {
|
|
@@ -308,6 +333,11 @@ static gboolean GObjectTeardownIdle(gpointer data) {
|
|
|
308
333
|
/* If the GObject was already finalized out from under us, GObjectFinalized
|
|
309
334
|
* cleared the pointer; there is nothing left to detach or unref. */
|
|
310
335
|
if (gobject != NULL) {
|
|
336
|
+
/* The weak ref that would cancel a queued off-thread toggle on
|
|
337
|
+
* finalize is removed below, so cancel any pending entry now — the
|
|
338
|
+
* toggle ref drop at the end may be the object's last reference. */
|
|
339
|
+
toggleQueue.Cancel (gobject);
|
|
340
|
+
|
|
311
341
|
/* Drop the weak ref first so removing the toggle ref (which may finalize
|
|
312
342
|
* the object) doesn't re-enter GObjectFinalized. */
|
|
313
343
|
g_object_weak_unref (gobject, GObjectFinalized, wrapper);
|
|
@@ -878,6 +908,17 @@ static MaybeLocal<FunctionTemplate> GetClassTemplate(GType gtype) {
|
|
|
878
908
|
if (maybeTpl.IsEmpty())
|
|
879
909
|
return MaybeLocal<FunctionTemplate> ();
|
|
880
910
|
|
|
911
|
+
/* NewClassTemplate() runs JS while building the parent chain (each
|
|
912
|
+
* ancestor's template fires the type materializer below), so JS may have
|
|
913
|
+
* re-entered here and created this very template already. Keep that one:
|
|
914
|
+
* its function is the one JS has started decorating. */
|
|
915
|
+
data = g_type_get_qdata (gtype, GNodeJS::template_quark());
|
|
916
|
+
if (data) {
|
|
917
|
+
auto *persistent = (Nan::Persistent<FunctionTemplate> *) data;
|
|
918
|
+
auto existingTpl = New<FunctionTemplate> (*persistent);
|
|
919
|
+
return existingTpl;
|
|
920
|
+
}
|
|
921
|
+
|
|
881
922
|
auto tpl = maybeTpl.ToLocalChecked();
|
|
882
923
|
auto fn = Nan::GetFunction (tpl).ToLocalChecked();
|
|
883
924
|
auto persistentTpl = new Nan::Persistent<FunctionTemplate>(tpl);
|
|
@@ -892,14 +933,19 @@ static MaybeLocal<FunctionTemplate> GetClassTemplate(GType gtype) {
|
|
|
892
933
|
g_type_set_qdata(gtype, GNodeJS::template_quark(), persistentTpl);
|
|
893
934
|
g_type_set_qdata(gtype, GNodeJS::function_quark(), persistentFn);
|
|
894
935
|
|
|
895
|
-
// Introspectable object types have their
|
|
896
|
-
// makeObject() in JS.
|
|
897
|
-
//
|
|
936
|
+
// Introspectable object types have their methods/properties installed by
|
|
937
|
+
// makeObject() in JS. Modules hold lazy accessors, so a type reached from
|
|
938
|
+
// C first (method return value, signal argument) must be materialized here
|
|
939
|
+
// or its wrappers would expose a bare prototype. Private concrete types
|
|
940
|
+
// are invisible to makeObject(), so mix in their interface methods
|
|
941
|
+
// instead (issue #441).
|
|
898
942
|
GIBaseInfo *own_info = g_irepository_find_by_gtype(NULL, gtype);
|
|
899
|
-
if (own_info == NULL)
|
|
943
|
+
if (own_info == NULL) {
|
|
900
944
|
ApplyInterfaceMethods(fn, gtype);
|
|
901
|
-
else
|
|
945
|
+
} else {
|
|
946
|
+
GNodeJS::MaterializeType(own_info);
|
|
902
947
|
g_base_info_unref(own_info);
|
|
948
|
+
}
|
|
903
949
|
|
|
904
950
|
return MaybeLocal<FunctionTemplate> (tpl);
|
|
905
951
|
}
|
package/src/gobject.h
CHANGED
|
@@ -18,6 +18,11 @@ namespace GNodeJS {
|
|
|
18
18
|
MaybeLocal<Function> MakeClass (GIBaseInfo *info);
|
|
19
19
|
Local<Value> WrapperFromGObject (GObject *object);
|
|
20
20
|
GObject * GObjectFromWrapper (Local<Value> value);
|
|
21
|
+
|
|
22
|
+
/* Reconcile the wrapper's V8 persistent (weak or strong) with the GObject's
|
|
23
|
+
* current refcount. Main thread only; called inline by ToggleNotify on the JS
|
|
24
|
+
* thread and by toggleQueue's drain for deferred off-thread notifications. */
|
|
25
|
+
void SynchronizeToggleState (GObject *gobject);
|
|
21
26
|
Local<Value> GetSignalHandler (GObject *gobject, guint index);
|
|
22
27
|
Local<FunctionTemplate> GetBaseClassTemplate ();
|
|
23
28
|
MaybeLocal<Value> GetGObjectProperty (GObject * gobject, const char *prop_name);
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
|
|
2
|
+
#include "toggle_queue.h"
|
|
3
|
+
#include "gobject.h"
|
|
4
|
+
|
|
5
|
+
namespace GNodeJS {
|
|
6
|
+
|
|
7
|
+
ToggleQueue toggleQueue;
|
|
8
|
+
|
|
9
|
+
void ToggleQueue::Synchronize (GObject *gobject) {
|
|
10
|
+
g_mutex_lock (&lock);
|
|
11
|
+
if (g_slist_find (objects, gobject) == NULL)
|
|
12
|
+
objects = g_slist_prepend (objects, gobject);
|
|
13
|
+
if (source == 0)
|
|
14
|
+
source = g_idle_add (ToggleQueue::Drain, this);
|
|
15
|
+
g_mutex_unlock (&lock);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
void ToggleQueue::Cancel (GObject *gobject) {
|
|
19
|
+
g_mutex_lock (&lock);
|
|
20
|
+
objects = g_slist_remove (objects, gobject);
|
|
21
|
+
g_mutex_unlock (&lock);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/* Runs on the main context. The lock is held across the drain so a (buggy,
|
|
25
|
+
* over-unreffed) off-thread finalize cancelling its entry cannot race the
|
|
26
|
+
* entry being processed. SynchronizeToggleState never re-enters the queue,
|
|
27
|
+
* so this cannot deadlock. */
|
|
28
|
+
gboolean ToggleQueue::Drain (gpointer user_data) {
|
|
29
|
+
auto *self = static_cast<ToggleQueue *> (user_data);
|
|
30
|
+
|
|
31
|
+
g_mutex_lock (&self->lock);
|
|
32
|
+
GSList *objects = self->objects;
|
|
33
|
+
self->objects = NULL;
|
|
34
|
+
self->source = 0;
|
|
35
|
+
|
|
36
|
+
for (GSList *l = objects; l != NULL; l = l->next)
|
|
37
|
+
SynchronizeToggleState ((GObject *) l->data);
|
|
38
|
+
|
|
39
|
+
g_slist_free (objects);
|
|
40
|
+
g_mutex_unlock (&self->lock);
|
|
41
|
+
return G_SOURCE_REMOVE;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
}; /* GNodeJS */
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
|
|
2
|
+
#pragma once
|
|
3
|
+
|
|
4
|
+
#include <glib-object.h>
|
|
5
|
+
|
|
6
|
+
namespace GNodeJS {
|
|
7
|
+
|
|
8
|
+
/*
|
|
9
|
+
* Defers toggle-state synchronization to the main context.
|
|
10
|
+
*
|
|
11
|
+
* "Synchronize" is meant as reconcile — bring the wrapper's V8 persistent
|
|
12
|
+
* (weak or strong) in line with the GObject's current refcount — not as
|
|
13
|
+
* synchronous execution: requests may come from any thread, and the
|
|
14
|
+
* reconciliation itself always runs later, from an idle on the main context.
|
|
15
|
+
*
|
|
16
|
+
* A toggle notification runs on whatever thread crosses the 1<->2 refcount
|
|
17
|
+
* boundary, but V8 global handles may only be touched from the JS thread
|
|
18
|
+
* (see ToggleNotify in gobject.cc). Off-thread notifications land here.
|
|
19
|
+
*
|
|
20
|
+
* The queue stores no direction: by the time the idle runs, the notified
|
|
21
|
+
* direction is stale (the refcount may have crossed the boundary again, in
|
|
22
|
+
* either order). Each drained object is instead reconciled from its current
|
|
23
|
+
* refcount (SynchronizeToggleState), which makes the deferral idempotent and
|
|
24
|
+
* ordering-insensitive.
|
|
25
|
+
*
|
|
26
|
+
* Entries hold no reference (taking one from inside a toggle handler would
|
|
27
|
+
* recursively fire the opposite toggle). A live wrapper's toggle ref keeps
|
|
28
|
+
* the object's refcount >= 1, so a queued object cannot be finalized under
|
|
29
|
+
* normal operation; as a safety net the GObjectFinalized weak callback and
|
|
30
|
+
* the wrapper teardown Cancel() any pending entry.
|
|
31
|
+
*/
|
|
32
|
+
class ToggleQueue {
|
|
33
|
+
public:
|
|
34
|
+
/* Request a deferred synchronization of the object's toggle state.
|
|
35
|
+
* Callable from any thread. */
|
|
36
|
+
void Synchronize (GObject *gobject);
|
|
37
|
+
|
|
38
|
+
/* Drop the object's pending synchronization, if any. Callable from any
|
|
39
|
+
* thread. Must be called before the object can be finalized, so the
|
|
40
|
+
* drain never touches freed memory. */
|
|
41
|
+
void Cancel (GObject *gobject);
|
|
42
|
+
|
|
43
|
+
private:
|
|
44
|
+
static gboolean Drain (gpointer user_data);
|
|
45
|
+
|
|
46
|
+
GMutex lock;
|
|
47
|
+
GSList *objects;
|
|
48
|
+
guint source;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
extern ToggleQueue toggleQueue;
|
|
52
|
+
|
|
53
|
+
}; /* GNodeJS */
|
package/src/value.cc
CHANGED
|
@@ -1293,15 +1293,24 @@ void FreeGIArgument(GITypeInfo *type_info, GIArgument *arg, GITransfer transfer,
|
|
|
1293
1293
|
if (free_elements) {
|
|
1294
1294
|
GITypeInfo *element_info = g_type_info_get_param_type(type_info, 0);
|
|
1295
1295
|
|
|
1296
|
-
|
|
1297
|
-
|
|
1296
|
+
/* Free elements in the direction they were allocated: an
|
|
1297
|
+
* OUT/EVERYTHING list transferred us one reference per element,
|
|
1298
|
+
* released here (the wrappers hold their own). An IN list was
|
|
1299
|
+
* built by V8ToGIArgument, which allocates strings but borrows
|
|
1300
|
+
* GObject/boxed pointers straight from their wrappers — freeing
|
|
1301
|
+
* with IN/NOTHING releases the strings and leaves the borrowed
|
|
1302
|
+
* pointers alone (mirrors FreeGIArgumentArray). Unreffing IN
|
|
1303
|
+
* elements stole a reference per element from their real owners
|
|
1304
|
+
* (e.g. Gdk.FileList.newFromList: the GFiles were finalized while
|
|
1305
|
+
* the GdkFileList's deep copy still pointed at them). */
|
|
1306
|
+
GITransfer element_transfer = is_in ? GI_TRANSFER_NOTHING : GI_TRANSFER_EVERYTHING;
|
|
1298
1307
|
GIArgument element_arg;
|
|
1299
1308
|
|
|
1300
1309
|
GSList* list = (GSList *)arg->v_pointer;
|
|
1301
1310
|
|
|
1302
1311
|
for (; list != NULL; list = list->next) {
|
|
1303
1312
|
element_arg.v_pointer = list->data;
|
|
1304
|
-
FreeGIArgument(element_info, &element_arg, element_transfer,
|
|
1313
|
+
FreeGIArgument(element_info, &element_arg, element_transfer, direction);
|
|
1305
1314
|
}
|
|
1306
1315
|
|
|
1307
1316
|
g_base_info_unref(element_info);
|
|
@@ -1702,6 +1711,14 @@ void RefObjectForTransferFullIn (GITypeInfo *type_info, GIArgument *arg) {
|
|
|
1702
1711
|
g_base_info_unref(iface);
|
|
1703
1712
|
}
|
|
1704
1713
|
|
|
1714
|
+
// Documented on the declaration in value.h.
|
|
1715
|
+
void TakeOwnershipForTransferFull (GITypeInfo *type_info, GIArgument *arg, long length) {
|
|
1716
|
+
CopyBoxedForTransferFullIn(type_info, arg, length);
|
|
1717
|
+
RefObjectForTransferFullIn(type_info, arg);
|
|
1718
|
+
RefFundamentalForTransferFullIn(type_info, arg);
|
|
1719
|
+
RefVariantForTransferFullIn(type_info, arg);
|
|
1720
|
+
}
|
|
1721
|
+
|
|
1705
1722
|
|
|
1706
1723
|
/*
|
|
1707
1724
|
* GValue conversion functions
|
package/src/value.h
CHANGED
|
@@ -52,6 +52,16 @@ void CopyBoxedForTransferFullIn (GITypeInfo *type_info, GIArgument *arg,
|
|
|
52
52
|
// under the callee once the wrapper is GC'd. See #439.
|
|
53
53
|
void RefObjectForTransferFullIn (GITypeInfo *type_info, GIArgument *arg);
|
|
54
54
|
|
|
55
|
+
// Hand a transfer-full value its own reference for its new owner. A value whose
|
|
56
|
+
// ownership transfers full — a transfer-full IN argument, or a transfer-full
|
|
57
|
+
// callable return — is exactly one of boxed / GObject / fundamental / GVariant,
|
|
58
|
+
// and the four per-kind helpers (CopyBoxedForTransferFullIn / RefObjectFor... /
|
|
59
|
+
// RefFundamentalFor... / RefVariantFor...) each no-op for the other kinds. Runs
|
|
60
|
+
// them all so the single call covers every kind. `length` is the element count
|
|
61
|
+
// for a transfer-full C array of boxed pointers (-1 when not such an array).
|
|
62
|
+
// See #439/#409/#468.
|
|
63
|
+
void TakeOwnershipForTransferFull (GITypeInfo *type_info, GIArgument *arg, long length);
|
|
64
|
+
|
|
55
65
|
bool CanConvertV8ToGIArgument (GITypeInfo *type_info, Local<Value> value, bool may_be_null);
|
|
56
66
|
|
|
57
67
|
bool V8ToGValue(GValue *gvalue, Local<Value> value, ResourceOwnership ownership = kNone);
|