node-gtk 4.0.0 → 4.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/lib/bootstrap.js CHANGED
@@ -2,9 +2,21 @@
2
2
  * bootstrap.js
3
3
  */
4
4
 
5
- const camelCase = require('lodash.camelcase')
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/loop.js CHANGED
@@ -45,10 +45,22 @@ function start() {
45
45
  * macrotask lets the module's top-level microtask return first, so the queue
46
46
  * drains and the loop integration takes over from a clean (non-nested) state.
47
47
  *
48
+ * We defer with setTimeout, NOT setImmediate. A setImmediate callback runs
49
+ * *inside* Node's immediate-processing machinery (native CheckImmediate ->
50
+ * processImmediate). Because `run` never returns (it blocks in the GLib main
51
+ * loop until the app quits), CheckImmediate never reaches the code that stops
52
+ * Node's private immediate uv_idle handle, so that handle stays active forever.
53
+ * An active idle handle pins uv_backend_timeout() at 0, which makes the nested
54
+ * uv-in-GLib loop busy-spin at 100% CPU (worse on Node 26 / libuv 1.52, where
55
+ * the immediate's wakeup eventfd also stays signalled). A one-shot timer is
56
+ * removed from libuv's timer heap before its callback runs, so blocking inside
57
+ * it leaves no libuv state active and the loop can sleep normally. See #477.
58
+ *
48
59
  * Under CommonJS (and inside signal callbacks) we are not in a microtask, so we
49
60
  * run synchronously and preserve the original blocking semantics exactly.
50
61
  *
51
62
  * https://github.com/romgrk/node-gtk/issues/442
63
+ * https://github.com/romgrk/node-gtk/issues/477
52
64
  *
53
65
  * Returns the native call's result when run synchronously (so e.g.
54
66
  * `const status = app.run()` keeps working under CommonJS); returns undefined
@@ -64,7 +76,7 @@ function runLoopEntry(run) {
64
76
  start()
65
77
 
66
78
  if (internal.IsRunningMicrotasks()) {
67
- setImmediate(run)
79
+ setTimeout(run, 0)
68
80
  return undefined
69
81
  }
70
82
  return run()
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, makeInfo, getInfoName } = require('./bootstrap.js')
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
- const nInfos = GI.Repository_get_n_infos.call(repo, ns);
41
- for (let i = 0; i < nInfos; i++) {
42
- const info = GI.Repository_get_info.call(repo, ns, i);
43
- const item = makeInfo(info);
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
- const packagePath = path.resolve(path.join(__dirname,'../package.json'))
10
- const bindingPath = binary.find(packagePath)
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.0.0",
3
+ "version": "4.1.0",
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": "^1.0.10",
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.452.0",
65
+ "aws-sdk": "^2.1692.0",
66
66
  "chalk": "^2.4.2",
67
- "mocha": "^7.1.0",
68
- "nid-parser": "0.0.5",
69
- "node-pre-gyp-github": "^1.4.5"
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
- if (Nan::HasOwnProperty(module, name).FromMaybe(false)) {
434
- auto constructor = TO_OBJECT (Nan::Get(module, name).ToLocalChecked());
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
@@ -220,6 +220,18 @@ void Callback::Execute (GIArgument *result, GIArgument **args, Callback *callbac
220
220
  Throw::InvalidReturnValue (&return_type_info, jsReturnValue);
221
221
  goto out;
222
222
  }
223
+
224
+ // When the callback's return value is transfer-full, the C caller takes
225
+ // ownership of it (e.g. GtkTreeListModelCreateModelFunc → GListModel).
226
+ // V8ToGIArgument only unwrapped the pointer, leaving node-gtk holding
227
+ // just its toggle ref; without the owning reference the caller "owns"
228
+ // that toggle ref, so no toggle-up fires, the wrapper stays weak, and
229
+ // once GC collects it the object is finalized while the caller still
230
+ // uses it — a use-after-free (e.g. gtk_tree_list_model_finalize
231
+ // disconnecting its items-changed handler from a freed child model).
232
+ // This is the return counterpart of the transfer-full IN argument.
233
+ if (g_callable_info_get_caller_owns(callback->info) == GI_TRANSFER_EVERYTHING)
234
+ TakeOwnershipForTransferFull(&return_type_info, result, -1);
223
235
  }
224
236
 
225
237
  // 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
- CopyBoxedForTransferFullIn(&type_info, &callable_arg_values[i], param.length);
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
 
@@ -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
@@ -35,6 +35,32 @@ namespace GNodeJS {
35
35
  Local<Object> GetModuleCache() {
36
36
  return Nan::New<Object>(GNodeJS::moduleCache);
37
37
  }
38
+
39
+ static Nan::Persistent<v8::Function> typeMaterializer;
40
+
41
+ void SetTypeMaterializerInternal(Local<v8::Function> fn) {
42
+ typeMaterializer.Reset(fn);
43
+ }
44
+
45
+ void MaterializeType(GIBaseInfo *info) {
46
+ if (typeMaterializer.IsEmpty() || info == NULL)
47
+ return;
48
+
49
+ Local<v8::Function> fn = Nan::New<v8::Function>(typeMaterializer);
50
+ Local<Value> argv[] = {
51
+ UTF8(g_base_info_get_namespace(info)),
52
+ UTF8(g_base_info_get_name(info)),
53
+ };
54
+ Nan::TryCatch tryCatch;
55
+ Nan::Call(fn, Nan::GetCurrentContext()->Global(), 2, argv);
56
+ if (tryCatch.HasCaught()) {
57
+ Nan::Utf8String message(tryCatch.Exception());
58
+ g_warning("node-gtk: could not materialize type %s.%s: %s",
59
+ g_base_info_get_namespace(info),
60
+ g_base_info_get_name(info),
61
+ *message);
62
+ }
63
+ }
38
64
  }
39
65
 
40
66
 
@@ -121,6 +147,71 @@ NAN_METHOD(Bootstrap) {
121
147
  info.GetReturnValue().Set(module_obj);
122
148
  }
123
149
 
150
+ /*
151
+ * Enumerate a loaded namespace's top-level infos in one native call, so that
152
+ * lib/module.js can define lazy accessors without paying ~3 FFI round-trips
153
+ * per info (48ms for Gtk-4.0 + dependencies, vs ~1ms here). Returns a flat
154
+ * array of [name, infoType, index, ...] triplets. Infos that makeInfo() cannot
155
+ * handle (callbacks, gtype structs, etc.) are filtered out here, mirroring the
156
+ * `item !== undefined` check the eager loop used to do.
157
+ */
158
+ NAN_METHOD(GetInfoEntries) {
159
+ Nan::Utf8String ns(info[0]);
160
+ GIRepository *repo = g_irepository_get_default();
161
+
162
+ int n = g_irepository_get_n_infos(repo, *ns);
163
+ Local<Array> result = Nan::New<Array>();
164
+ uint32_t position = 0;
165
+
166
+ for (int i = 0; i < n; i++) {
167
+ BaseInfo baseInfo(g_irepository_get_info(repo, *ns, i));
168
+ GIInfoType type = baseInfo.type();
169
+
170
+ switch (type) {
171
+ case GI_INFO_TYPE_FUNCTION:
172
+ case GI_INFO_TYPE_BOXED:
173
+ case GI_INFO_TYPE_ENUM:
174
+ case GI_INFO_TYPE_FLAGS:
175
+ case GI_INFO_TYPE_OBJECT:
176
+ case GI_INFO_TYPE_INTERFACE:
177
+ case GI_INFO_TYPE_CONSTANT:
178
+ case GI_INFO_TYPE_UNION:
179
+ break;
180
+ case GI_INFO_TYPE_STRUCT:
181
+ if (g_struct_info_is_gtype_struct(baseInfo.info()))
182
+ continue;
183
+ break;
184
+ default:
185
+ continue;
186
+ }
187
+
188
+ /* Register the GType with the GObject runtime NOW, even though the JS
189
+ * class stays lazy. The eager loop did this as a side effect
190
+ * (MakeObjectClass/MakeBoxedClass call get_g_type() for every class)
191
+ * and code depends on it: by-name lookups such as
192
+ * GObject.typeFromName('GFileInfo') or type names in GtkBuilder XML
193
+ * must resolve without JS ever touching the class. Registration is a
194
+ * few µs per type; class initialization stays lazy either way. */
195
+ switch (type) {
196
+ case GI_INFO_TYPE_STRUCT:
197
+ case GI_INFO_TYPE_BOXED:
198
+ case GI_INFO_TYPE_UNION:
199
+ case GI_INFO_TYPE_OBJECT:
200
+ case GI_INFO_TYPE_INTERFACE:
201
+ g_registered_type_info_get_g_type((GIRegisteredTypeInfo *) baseInfo.info());
202
+ break;
203
+ default:
204
+ break;
205
+ }
206
+
207
+ Nan::Set(result, position++, UTF8(baseInfo.name()));
208
+ Nan::Set(result, position++, Nan::New<v8::Int32>(type));
209
+ Nan::Set(result, position++, Nan::New<v8::Int32>(i));
210
+ }
211
+
212
+ info.GetReturnValue().Set(result);
213
+ }
214
+
124
215
  NAN_METHOD(GetConstantValue) {
125
216
  GIBaseInfo *gi_info = (GIBaseInfo *) GNodeJS::PointerFromWrapper (info[0]);
126
217
  GITypeInfo *type_info = g_constant_info_get_type(gi_info);
@@ -418,6 +509,10 @@ NAN_METHOD(SetInterfaceMethodsApplier) {
418
509
  GNodeJS::SetInterfaceMethodsApplier(info);
419
510
  }
420
511
 
512
+ NAN_METHOD(SetTypeMaterializer) {
513
+ GNodeJS::SetTypeMaterializerInternal(info[0].As<v8::Function>());
514
+ }
515
+
421
516
  NAN_METHOD(RegisterClass) {
422
517
  GNodeJS::ObjectClass::RegisterClass(info);
423
518
  }
@@ -438,6 +533,8 @@ void InitModule(Local<Object> exports, Local<Value> module, void *priv) {
438
533
  Nan::Export(exports, "GetBaseClass", GetBaseClass);
439
534
  Nan::Export(exports, "GetTypeSize", GetTypeSize);
440
535
  Nan::Export(exports, "GetConstantValue", GetConstantValue);
536
+ Nan::Export(exports, "GetInfoEntries", GetInfoEntries);
537
+ Nan::Export(exports, "SetTypeMaterializer", SetTypeMaterializer);
441
538
  Nan::Export(exports, "MakeBoxedClass", MakeBoxedClass);
442
539
  Nan::Export(exports, "MakeObjectClass", MakeObjectClass);
443
540
  Nan::Export(exports, "MakeFunction", MakeFunction);
package/src/gi.h CHANGED
@@ -26,6 +26,21 @@ extern Nan::Persistent<Object> moduleCache;
26
26
 
27
27
  Local<Object> GetModuleCache();
28
28
 
29
+ /*
30
+ * Lazy type materialization: modules are populated with lazy accessors
31
+ * (lib/module.js), and prototypes only get their methods/properties when a
32
+ * class is first touched. Types can also be reached from C first (a method
33
+ * return value, a signal argument): the template-creation paths in
34
+ * gobject.cc/boxed.cc/fundamental.cc call MaterializeType so the JS side
35
+ * decorates the prototype before any wrapper is handed out. The callback is
36
+ * installed from bootstrap.js via SetTypeMaterializer and is idempotent and
37
+ * re-entrancy-safe on the JS side.
38
+ */
39
+
40
+ void MaterializeType(GIBaseInfo *info);
41
+
42
+ void SetTypeMaterializerInternal(Local<v8::Function> fn);
43
+
29
44
 
30
45
 
31
46
  /*
package/src/gobject.cc CHANGED
@@ -878,6 +878,17 @@ static MaybeLocal<FunctionTemplate> GetClassTemplate(GType gtype) {
878
878
  if (maybeTpl.IsEmpty())
879
879
  return MaybeLocal<FunctionTemplate> ();
880
880
 
881
+ /* NewClassTemplate() runs JS while building the parent chain (each
882
+ * ancestor's template fires the type materializer below), so JS may have
883
+ * re-entered here and created this very template already. Keep that one:
884
+ * its function is the one JS has started decorating. */
885
+ data = g_type_get_qdata (gtype, GNodeJS::template_quark());
886
+ if (data) {
887
+ auto *persistent = (Nan::Persistent<FunctionTemplate> *) data;
888
+ auto existingTpl = New<FunctionTemplate> (*persistent);
889
+ return existingTpl;
890
+ }
891
+
881
892
  auto tpl = maybeTpl.ToLocalChecked();
882
893
  auto fn = Nan::GetFunction (tpl).ToLocalChecked();
883
894
  auto persistentTpl = new Nan::Persistent<FunctionTemplate>(tpl);
@@ -892,14 +903,19 @@ static MaybeLocal<FunctionTemplate> GetClassTemplate(GType gtype) {
892
903
  g_type_set_qdata(gtype, GNodeJS::template_quark(), persistentTpl);
893
904
  g_type_set_qdata(gtype, GNodeJS::function_quark(), persistentFn);
894
905
 
895
- // Introspectable object types have their interface methods installed by
896
- // makeObject() in JS. Private concrete types are never seen there, so mix
897
- // in their interface methods now (issue #441).
906
+ // Introspectable object types have their methods/properties installed by
907
+ // makeObject() in JS. Modules hold lazy accessors, so a type reached from
908
+ // C first (method return value, signal argument) must be materialized here
909
+ // or its wrappers would expose a bare prototype. Private concrete types
910
+ // are invisible to makeObject(), so mix in their interface methods
911
+ // instead (issue #441).
898
912
  GIBaseInfo *own_info = g_irepository_find_by_gtype(NULL, gtype);
899
- if (own_info == NULL)
913
+ if (own_info == NULL) {
900
914
  ApplyInterfaceMethods(fn, gtype);
901
- else
915
+ } else {
916
+ GNodeJS::MaterializeType(own_info);
902
917
  g_base_info_unref(own_info);
918
+ }
903
919
 
904
920
  return MaybeLocal<FunctionTemplate> (tpl);
905
921
  }
package/src/value.cc CHANGED
@@ -1702,6 +1702,14 @@ void RefObjectForTransferFullIn (GITypeInfo *type_info, GIArgument *arg) {
1702
1702
  g_base_info_unref(iface);
1703
1703
  }
1704
1704
 
1705
+ // Documented on the declaration in value.h.
1706
+ void TakeOwnershipForTransferFull (GITypeInfo *type_info, GIArgument *arg, long length) {
1707
+ CopyBoxedForTransferFullIn(type_info, arg, length);
1708
+ RefObjectForTransferFullIn(type_info, arg);
1709
+ RefFundamentalForTransferFullIn(type_info, arg);
1710
+ RefVariantForTransferFullIn(type_info, arg);
1711
+ }
1712
+
1705
1713
 
1706
1714
  /*
1707
1715
  * 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);